jquery.flot.js ➔ Plot   F
last analyzed

Complexity

Conditions 562

Size

Total Lines 2633
Code Lines 1643

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1643
dl 0
loc 2633
rs 0
c 0
b 0
f 0
cc 562

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like jquery.flot.js ➔ Plot often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/* Javascript plotting library for jQuery, version 0.8.3.
2
3
Copyright (c) 2007-2014 IOLA and Ole Laursen.
4
Licensed under the MIT license.
5
6
*/
7
8
// first an inline dependency, jquery.colorhelpers.js, we inline it here
9
// for convenience
10
11
/* Plugin for jQuery for working with colors.
12
 *
13
 * Version 1.1.
14
 *
15
 * Inspiration from jQuery color animation plugin by John Resig.
16
 *
17
 * Released under the MIT license by Ole Laursen, October 2009.
18
 *
19
 * Examples:
20
 *
21
 *   $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
22
 *   var c = $.color.extract($("#mydiv"), 'background-color');
23
 *   console.log(c.r, c.g, c.b, c.a);
24
 *   $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
25
 *
26
 * Note that .scale() and .add() return the same modified object
27
 * instead of making a new one.
28
 *
29
 * V. 1.1: Fix error handling so e.g. parsing an empty string does
30
 * produce a color rather than just crashing.
31
 */
32
(function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i<c.length;++i)o[c.charAt(i)]+=d;return o.normalize()};o.scale=function(c,f){for(var i=0;i<c.length;++i)o[c.charAt(i)]*=f;return o.normalize()};o.toString=function(){if(o.a>=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return value<min?min:value>max?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);
33
34
// the actual Flot code
35
(function($) {
36
37
	// Cache the prototype hasOwnProperty for faster access
38
39
	var hasOwnProperty = Object.prototype.hasOwnProperty;
40
41
    // A shim to provide 'detach' to jQuery versions prior to 1.4.  Using a DOM
42
    // operation produces the same effect as detach, i.e. removing the element
43
    // without touching its jQuery data.
44
45
    // Do not merge this into Flot 0.9, since it requires jQuery 1.4.4+.
46
47
    if (!$.fn.detach) {
48
        $.fn.detach = function() {
49
            return this.each(function() {
50
                if (this.parentNode) {
51
                    this.parentNode.removeChild( this );
52
                }
53
            });
54
        };
55
    }
56
57
	///////////////////////////////////////////////////////////////////////////
58
	// The Canvas object is a wrapper around an HTML5 <canvas> tag.
59
	//
60
	// @constructor
61
	// @param {string} cls List of classes to apply to the canvas.
62
	// @param {element} container Element onto which to append the canvas.
63
	//
64
	// Requiring a container is a little iffy, but unfortunately canvas
65
	// operations don't work unless the canvas is attached to the DOM.
66
67
	function Canvas(cls, container) {
68
69
		var element = container.children("." + cls)[0];
70
71
		if (element == null) {
72
73
			element = document.createElement("canvas");
74
			element.className = cls;
75
76
			$(element).css({ direction: "ltr", position: "absolute", left: 0, top: 0 })
77
				.appendTo(container);
78
79
			// If HTML5 Canvas isn't available, fall back to [Ex|Flash]canvas
80
81
			if (!element.getContext) {
82
				if (window.G_vmlCanvasManager) {
83
					element = window.G_vmlCanvasManager.initElement(element);
84
				} else {
85
					throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode.");
86
				}
87
			}
88
		}
89
90
		this.element = element;
91
92
		var context = this.context = element.getContext("2d");
93
94
		// Determine the screen's ratio of physical to device-independent
95
		// pixels.  This is the ratio between the canvas width that the browser
96
		// advertises and the number of pixels actually present in that space.
97
98
		// The iPhone 4, for example, has a device-independent width of 320px,
99
		// but its screen is actually 640px wide.  It therefore has a pixel
100
		// ratio of 2, while most normal devices have a ratio of 1.
101
102
		var devicePixelRatio = window.devicePixelRatio || 1,
103
			backingStoreRatio =
104
				context.webkitBackingStorePixelRatio ||
105
				context.mozBackingStorePixelRatio ||
106
				context.msBackingStorePixelRatio ||
107
				context.oBackingStorePixelRatio ||
108
				context.backingStorePixelRatio || 1;
109
110
		this.pixelRatio = devicePixelRatio / backingStoreRatio;
111
112
		// Size the canvas to match the internal dimensions of its container
113
114
		this.resize(container.width(), container.height());
115
116
		// Collection of HTML div layers for text overlaid onto the canvas
117
118
		this.textContainer = null;
119
		this.text = {};
120
121
		// Cache of text fragments and metrics, so we can avoid expensively
122
		// re-calculating them when the plot is re-rendered in a loop.
123
124
		this._textCache = {};
125
	}
126
127
	// Resizes the canvas to the given dimensions.
128
	//
129
	// @param {number} width New width of the canvas, in pixels.
130
	// @param {number} width New height of the canvas, in pixels.
131
132
	Canvas.prototype.resize = function(width, height) {
133
134
		if (width <= 0 || height <= 0) {
135
			throw new Error("Invalid dimensions for plot, width = " + width + ", height = " + height);
136
		}
137
138
		var element = this.element,
139
			context = this.context,
140
			pixelRatio = this.pixelRatio;
141
142
		// Resize the canvas, increasing its density based on the display's
143
		// pixel ratio; basically giving it more pixels without increasing the
144
		// size of its element, to take advantage of the fact that retina
145
		// displays have that many more pixels in the same advertised space.
146
147
		// Resizing should reset the state (excanvas seems to be buggy though)
148
149
		if (this.width != width) {
150
			element.width = width * pixelRatio;
151
			element.style.width = width + "px";
152
			this.width = width;
153
		}
154
155
		if (this.height != height) {
156
			element.height = height * pixelRatio;
157
			element.style.height = height + "px";
158
			this.height = height;
159
		}
160
161
		// Save the context, so we can reset in case we get replotted.  The
162
		// restore ensure that we're really back at the initial state, and
163
		// should be safe even if we haven't saved the initial state yet.
164
165
		context.restore();
166
		context.save();
167
168
		// Scale the coordinate space to match the display density; so even though we
169
		// may have twice as many pixels, we still want lines and other drawing to
170
		// appear at the same size; the extra pixels will just make them crisper.
171
172
		context.scale(pixelRatio, pixelRatio);
173
	};
174
175
	// Clears the entire canvas area, not including any overlaid HTML text
176
177
	Canvas.prototype.clear = function() {
178
		this.context.clearRect(0, 0, this.width, this.height);
179
	};
180
181
	// Finishes rendering the canvas, including managing the text overlay.
182
183
	Canvas.prototype.render = function() {
184
185
		var cache = this._textCache;
186
187
		// For each text layer, add elements marked as active that haven't
188
		// already been rendered, and remove those that are no longer active.
189
190
		for (var layerKey in cache) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
191
			if (hasOwnProperty.call(cache, layerKey)) {
192
193
				var layer = this.getTextLayer(layerKey),
194
					layerCache = cache[layerKey];
195
196
				layer.hide();
197
198
				for (var styleKey in layerCache) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
199
					if (hasOwnProperty.call(layerCache, styleKey)) {
200
						var styleCache = layerCache[styleKey];
201
						for (var key in styleCache) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
202
							if (hasOwnProperty.call(styleCache, key)) {
203
204
								var positions = styleCache[key].positions;
205
206
								for (var i = 0, position; position = positions[i]; i++) {
207
									if (position.active) {
208
										if (!position.rendered) {
209
											layer.append(position.element);
210
											position.rendered = true;
211
										}
212
									} else {
213
										positions.splice(i--, 1);
0 ignored issues
show
Complexity Coding Style introduced by
You seem to be assigning a new value to the loop variable i here. Please check if this was indeed your intention. Even if it was, consider using another kind of loop instead.
Loading history...
214
										if (position.rendered) {
215
											position.element.detach();
216
										}
217
									}
218
								}
219
220
								if (positions.length == 0) {
221
									delete styleCache[key];
222
								}
223
							}
224
						}
225
					}
226
				}
227
228
				layer.show();
229
			}
230
		}
231
	};
232
233
	// Creates (if necessary) and returns the text overlay container.
234
	//
235
	// @param {string} classes String of space-separated CSS classes used to
236
	//     uniquely identify the text layer.
237
	// @return {object} The jQuery-wrapped text-layer div.
238
239
	Canvas.prototype.getTextLayer = function(classes) {
240
241
		var layer = this.text[classes];
242
243
		// Create the text layer if it doesn't exist
244
245
		if (layer == null) {
246
247
			// Create the text layer container, if it doesn't exist
248
249
			if (this.textContainer == null) {
250
				this.textContainer = $("<div class='flot-text'></div>")
251
					.css({
252
						position: "absolute",
253
						top: 0,
254
						left: 0,
255
						bottom: 0,
256
						right: 0,
257
						'font-size': "smaller",
258
						color: "#545454"
259
					})
260
					.insertAfter(this.element);
261
			}
262
263
			layer = this.text[classes] = $("<div></div>")
264
				.addClass(classes)
265
				.css({
266
					position: "absolute",
267
					top: 0,
268
					left: 0,
269
					bottom: 0,
270
					right: 0
271
				})
272
				.appendTo(this.textContainer);
273
		}
274
275
		return layer;
276
	};
277
278
	// Creates (if necessary) and returns a text info object.
279
	//
280
	// The object looks like this:
281
	//
282
	// {
283
	//     width: Width of the text's wrapper div.
284
	//     height: Height of the text's wrapper div.
285
	//     element: The jQuery-wrapped HTML div containing the text.
286
	//     positions: Array of positions at which this text is drawn.
287
	// }
288
	//
289
	// The positions array contains objects that look like this:
290
	//
291
	// {
292
	//     active: Flag indicating whether the text should be visible.
293
	//     rendered: Flag indicating whether the text is currently visible.
294
	//     element: The jQuery-wrapped HTML div containing the text.
295
	//     x: X coordinate at which to draw the text.
296
	//     y: Y coordinate at which to draw the text.
297
	// }
298
	//
299
	// Each position after the first receives a clone of the original element.
300
	//
301
	// The idea is that that the width, height, and general 'identity' of the
302
	// text is constant no matter where it is placed; the placements are a
303
	// secondary property.
304
	//
305
	// Canvas maintains a cache of recently-used text info objects; getTextInfo
306
	// either returns the cached element or creates a new entry.
307
	//
308
	// @param {string} layer A string of space-separated CSS classes uniquely
309
	//     identifying the layer containing this text.
310
	// @param {string} text Text string to retrieve info for.
311
	// @param {(string|object)=} font Either a string of space-separated CSS
312
	//     classes or a font-spec object, defining the text's font and style.
313
	// @param {number=} angle Angle at which to rotate the text, in degrees.
314
	//     Angle is currently unused, it will be implemented in the future.
315
	// @param {number=} width Maximum width of the text before it wraps.
316
	// @return {object} a text info object.
317
318
	Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) {
319
320
		var textStyle, layerCache, styleCache, info;
321
322
		// Cast the value to a string, in case we were given a number or such
323
324
		text = "" + text;
325
326
		// If the font is a font-spec object, generate a CSS font definition
327
328
		if (typeof font === "object") {
329
			textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px/" + font.lineHeight + "px " + font.family;
330
		} else {
331
			textStyle = font;
332
		}
333
334
		// Retrieve (or create) the cache for the text's layer and styles
335
336
		layerCache = this._textCache[layer];
337
338
		if (layerCache == null) {
339
			layerCache = this._textCache[layer] = {};
340
		}
341
342
		styleCache = layerCache[textStyle];
343
344
		if (styleCache == null) {
345
			styleCache = layerCache[textStyle] = {};
346
		}
347
348
		info = styleCache[text];
349
350
		// If we can't find a matching element in our cache, create a new one
351
352
		if (info == null) {
353
354
			var element = $("<div></div>").html(text)
355
				.css({
356
					position: "absolute",
357
					'max-width': width,
358
					top: -9999
359
				})
360
				.appendTo(this.getTextLayer(layer));
361
362
			if (typeof font === "object") {
363
				element.css({
364
					font: textStyle,
365
					color: font.color
366
				});
367
			} else if (typeof font === "string") {
368
				element.addClass(font);
369
			}
370
371
			info = styleCache[text] = {
372
				width: element.outerWidth(true),
373
				height: element.outerHeight(true),
374
				element: element,
375
				positions: []
376
			};
377
378
			element.detach();
379
		}
380
381
		return info;
382
	};
383
384
	// Adds a text string to the canvas text overlay.
385
	//
386
	// The text isn't drawn immediately; it is marked as rendering, which will
387
	// result in its addition to the canvas on the next render pass.
388
	//
389
	// @param {string} layer A string of space-separated CSS classes uniquely
390
	//     identifying the layer containing this text.
391
	// @param {number} x X coordinate at which to draw the text.
392
	// @param {number} y Y coordinate at which to draw the text.
393
	// @param {string} text Text string to draw.
394
	// @param {(string|object)=} font Either a string of space-separated CSS
395
	//     classes or a font-spec object, defining the text's font and style.
396
	// @param {number=} angle Angle at which to rotate the text, in degrees.
397
	//     Angle is currently unused, it will be implemented in the future.
398
	// @param {number=} width Maximum width of the text before it wraps.
399
	// @param {string=} halign Horizontal alignment of the text; either "left",
400
	//     "center" or "right".
401
	// @param {string=} valign Vertical alignment of the text; either "top",
402
	//     "middle" or "bottom".
403
404
	Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) {
405
406
		var info = this.getTextInfo(layer, text, font, angle, width),
407
			positions = info.positions;
408
409
		// Tweak the div's position to match the text's alignment
410
411
		if (halign == "center") {
412
			x -= info.width / 2;
413
		} else if (halign == "right") {
414
			x -= info.width;
415
		}
416
417
		if (valign == "middle") {
418
			y -= info.height / 2;
419
		} else if (valign == "bottom") {
420
			y -= info.height;
421
		}
422
423
		// Determine whether this text already exists at this position.
424
		// If so, mark it for inclusion in the next render pass.
425
426
		for (var i = 0, position; position = positions[i]; i++) {
427
			if (position.x == x && position.y == y) {
428
				position.active = true;
429
				return;
430
			}
431
		}
432
433
		// If the text doesn't exist at this position, create a new entry
434
435
		// For the very first position we'll re-use the original element,
436
		// while for subsequent ones we'll clone it.
437
438
		position = {
439
			active: true,
440
			rendered: false,
441
			element: positions.length ? info.element.clone() : info.element,
442
			x: x,
443
			y: y
444
		};
445
446
		positions.push(position);
447
448
		// Move the element to its final position within the container
449
450
		position.element.css({
451
			top: Math.round(y),
452
			left: Math.round(x),
453
			'text-align': halign	// In case the text wraps
454
		});
455
	};
456
457
	// Removes one or more text strings from the canvas text overlay.
458
	//
459
	// If no parameters are given, all text within the layer is removed.
460
	//
461
	// Note that the text is not immediately removed; it is simply marked as
462
	// inactive, which will result in its removal on the next render pass.
463
	// This avoids the performance penalty for 'clear and redraw' behavior,
464
	// where we potentially get rid of all text on a layer, but will likely
465
	// add back most or all of it later, as when redrawing axes, for example.
466
	//
467
	// @param {string} layer A string of space-separated CSS classes uniquely
468
	//     identifying the layer containing this text.
469
	// @param {number=} x X coordinate of the text.
470
	// @param {number=} y Y coordinate of the text.
471
	// @param {string=} text Text string to remove.
472
	// @param {(string|object)=} font Either a string of space-separated CSS
473
	//     classes or a font-spec object, defining the text's font and style.
474
	// @param {number=} angle Angle at which the text is rotated, in degrees.
475
	//     Angle is currently unused, it will be implemented in the future.
476
477
	Canvas.prototype.removeText = function(layer, x, y, text, font, angle) {
478
		if (text == null) {
479
			var layerCache = this._textCache[layer];
480
			if (layerCache != null) {
481
				for (var styleKey in layerCache) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
482
					if (hasOwnProperty.call(layerCache, styleKey)) {
483
						var styleCache = layerCache[styleKey];
484
						for (var key in styleCache) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
485
							if (hasOwnProperty.call(styleCache, key)) {
486
								var positions = styleCache[key].positions;
487
								for (var i = 0, position; position = positions[i]; i++) {
488
									position.active = false;
489
								}
490
							}
491
						}
492
					}
493
				}
494
			}
495
		} else {
496
			var positions = this.getTextInfo(layer, text, font, angle).positions;
497
			for (var i = 0, position; position = positions[i]; i++) {
498
				if (position.x == x && position.y == y) {
499
					position.active = false;
500
				}
501
			}
502
		}
503
	};
504
505
	///////////////////////////////////////////////////////////////////////////
506
	// The top-level container for the entire plot.
507
508
    function Plot(placeholder, data_, options_, plugins) {
509
        // data is on the form:
510
        //   [ series1, series2 ... ]
511
        // where series is either just the data as [ [x1, y1], [x2, y2], ... ]
512
        // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... }
513
514
        var series = [],
515
            options = {
516
                // the color theme used for graphs
517
                colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"],
518
                legend: {
519
                    show: true,
520
                    noColumns: 1, // number of colums in legend table
521
                    labelFormatter: null, // fn: string -> string
522
                    labelBoxBorderColor: "#ccc", // border color for the little label boxes
523
                    container: null, // container (as jQuery object) to put legend in, null means default on top of graph
524
                    position: "ne", // position of default legend container within plot
525
                    margin: 5, // distance from grid edge to default legend container within plot
526
                    backgroundColor: null, // null means auto-detect
527
                    backgroundOpacity: 0.85, // set to 0 to avoid background
528
                    sorted: null    // default to no legend sorting
529
                },
530
                xaxis: {
531
                    show: null, // null = auto-detect, true = always, false = never
532
                    position: "bottom", // or "top"
533
                    mode: null, // null or "time"
534
                    font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" }
535
                    color: null, // base color, labels, ticks
536
                    tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)"
537
                    transform: null, // null or f: number -> number to transform axis
538
                    inverseTransform: null, // if transform is set, this should be the inverse function
539
                    min: null, // min. value to show, null means set automatically
540
                    max: null, // max. value to show, null means set automatically
541
                    autoscaleMargin: null, // margin in % to add if auto-setting min/max
542
                    ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks
543
                    tickFormatter: null, // fn: number -> string
544
                    labelWidth: null, // size of tick labels in pixels
545
                    labelHeight: null,
546
                    reserveSpace: null, // whether to reserve space even if axis isn't shown
547
                    tickLength: null, // size in pixels of ticks, or "full" for whole line
548
                    alignTicksWithAxis: null, // axis number or null for no sync
549
                    tickDecimals: null, // no. of decimals, null means auto
550
                    tickSize: null, // number or [number, "unit"]
551
                    minTickSize: null // number or [number, "unit"]
552
                },
553
                yaxis: {
554
                    autoscaleMargin: 0.02,
555
                    position: "left" // or "right"
556
                },
557
                xaxes: [],
558
                yaxes: [],
559
                series: {
560
                    points: {
561
                        show: false,
562
                        radius: 3,
563
                        lineWidth: 2, // in pixels
564
                        fill: true,
565
                        fillColor: "#ffffff",
566
                        symbol: "circle" // or callback
567
                    },
568
                    lines: {
569
                        // we don't put in show: false so we can see
570
                        // whether lines were actively disabled
571
                        lineWidth: 2, // in pixels
572
                        fill: false,
573
                        fillColor: null,
574
                        steps: false
575
                        // Omit 'zero', so we can later default its value to
576
                        // match that of the 'fill' option.
577
                    },
578
                    bars: {
579
                        show: false,
580
                        lineWidth: 2, // in pixels
581
                        barWidth: 1, // in units of the x axis
582
                        fill: true,
583
                        fillColor: null,
584
                        align: "left", // "left", "right", or "center"
585
                        horizontal: false,
586
                        zero: true
587
                    },
588
                    shadowSize: 3,
589
                    highlightColor: null
590
                },
591
                grid: {
592
                    show: true,
593
                    aboveData: false,
594
                    color: "#545454", // primary color used for outline and labels
595
                    backgroundColor: null, // null for transparent, else color
596
                    borderColor: null, // set if different from the grid color
597
                    tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)"
598
                    margin: 0, // distance from the canvas edge to the grid
599
                    labelMargin: 5, // in pixels
600
                    axisMargin: 8, // in pixels
601
                    borderWidth: 2, // in pixels
602
                    minBorderMargin: null, // in pixels, null means taken from points radius
603
                    markings: null, // array of ranges or fn: axes -> array of ranges
604
                    markingsColor: "#f4f4f4",
605
                    markingsLineWidth: 2,
606
                    // interactive stuff
607
                    clickable: false,
608
                    hoverable: false,
609
                    autoHighlight: true, // highlight in case mouse is near
610
                    mouseActiveRadius: 10 // how far the mouse can be away to activate an item
611
                },
612
                interaction: {
613
                    redrawOverlayInterval: 1000/60 // time between updates, -1 means in same flow
614
                },
615
                hooks: {}
616
            },
617
        surface = null,     // the canvas for the plot itself
618
        overlay = null,     // canvas for interactive stuff on top of plot
619
        eventHolder = null, // jQuery object that events should be bound to
620
        ctx = null, octx = null,
621
        xaxes = [], yaxes = [],
622
        plotOffset = { left: 0, right: 0, top: 0, bottom: 0},
623
        plotWidth = 0, plotHeight = 0,
624
        hooks = {
625
            processOptions: [],
626
            processRawData: [],
627
            processDatapoints: [],
628
            processOffset: [],
629
            drawBackground: [],
630
            drawSeries: [],
631
            draw: [],
632
            bindEvents: [],
633
            drawOverlay: [],
634
            shutdown: []
635
        },
636
        plot = this;
637
638
        // public functions
639
        plot.setData = setData;
640
        plot.setupGrid = setupGrid;
641
        plot.draw = draw;
642
        plot.getPlaceholder = function() { return placeholder; };
643
        plot.getCanvas = function() { return surface.element; };
644
        plot.getPlotOffset = function() { return plotOffset; };
645
        plot.width = function () { return plotWidth; };
646
        plot.height = function () { return plotHeight; };
647
        plot.offset = function () {
648
            var o = eventHolder.offset();
649
            o.left += plotOffset.left;
650
            o.top += plotOffset.top;
651
            return o;
652
        };
653
        plot.getData = function () { return series; };
654
        plot.getAxes = function () {
655
            var res = {}, i;
0 ignored issues
show
Unused Code introduced by
The variable i seems to be never used. Consider removing it.
Loading history...
656
            $.each(xaxes.concat(yaxes), function (_, axis) {
657
                if (axis)
658
                    res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis;
659
            });
660
            return res;
661
        };
662
        plot.getXAxes = function () { return xaxes; };
663
        plot.getYAxes = function () { return yaxes; };
664
        plot.c2p = canvasToAxisCoords;
665
        plot.p2c = axisToCanvasCoords;
666
        plot.getOptions = function () { return options; };
667
        plot.highlight = highlight;
668
        plot.unhighlight = unhighlight;
669
        plot.triggerRedrawOverlay = triggerRedrawOverlay;
670
        plot.pointOffset = function(point) {
671
            return {
672
                left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left, 10),
673
                top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top, 10)
674
            };
675
        };
676
        plot.shutdown = shutdown;
677
        plot.destroy = function () {
678
            shutdown();
679
            placeholder.removeData("plot").empty();
680
681
            series = [];
682
            options = null;
683
            surface = null;
684
            overlay = null;
685
            eventHolder = null;
686
            ctx = null;
687
            octx = null;
688
            xaxes = [];
689
            yaxes = [];
690
            hooks = null;
691
            highlights = [];
692
            plot = null;
693
        };
694
        plot.resize = function () {
695
        	var width = placeholder.width(),
696
        		height = placeholder.height();
697
            surface.resize(width, height);
698
            overlay.resize(width, height);
699
        };
700
701
        // public attributes
702
        plot.hooks = hooks;
703
704
        // initialize
705
        initPlugins(plot);
0 ignored issues
show
Bug introduced by
The call to initPlugins seems to have too many arguments starting with plot.
Loading history...
706
        parseOptions(options_);
707
        setupCanvases();
708
        setData(data_);
709
        setupGrid();
710
        draw();
711
        bindEvents();
712
713
714
        function executeHooks(hook, args) {
715
            args = [plot].concat(args);
716
            for (var i = 0; i < hook.length; ++i)
717
                hook[i].apply(this, args);
718
        }
719
720
        function initPlugins() {
721
722
            // References to key classes, allowing plugins to modify them
723
724
            var classes = {
725
                Canvas: Canvas
726
            };
727
728
            for (var i = 0; i < plugins.length; ++i) {
729
                var p = plugins[i];
730
                p.init(plot, classes);
731
                if (p.options)
732
                    $.extend(true, options, p.options);
733
            }
734
        }
735
736
        function parseOptions(opts) {
737
738
            $.extend(true, options, opts);
739
740
            // $.extend merges arrays, rather than replacing them.  When less
741
            // colors are provided than the size of the default palette, we
742
            // end up with those colors plus the remaining defaults, which is
743
            // not expected behavior; avoid it by replacing them here.
744
745
            if (opts && opts.colors) {
746
            	options.colors = opts.colors;
747
            }
748
749
            if (options.xaxis.color == null)
750
                options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();
751
            if (options.yaxis.color == null)
752
                options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();
753
754
            if (options.xaxis.tickColor == null) // grid.tickColor for back-compatibility
755
                options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color;
756
            if (options.yaxis.tickColor == null) // grid.tickColor for back-compatibility
757
                options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color;
758
759
            if (options.grid.borderColor == null)
760
                options.grid.borderColor = options.grid.color;
761
            if (options.grid.tickColor == null)
762
                options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString();
763
764
            // Fill in defaults for axis options, including any unspecified
765
            // font-spec fields, if a font-spec was provided.
766
767
            // If no x/y axis options were provided, create one of each anyway,
768
            // since the rest of the code assumes that they exist.
769
770
            var i, axisOptions, axisCount,
771
                fontSize = placeholder.css("font-size"),
772
                fontSizeDefault = fontSize ? +fontSize.replace("px", "") : 13,
773
                fontDefaults = {
774
                    style: placeholder.css("font-style"),
775
                    size: Math.round(0.8 * fontSizeDefault),
776
                    variant: placeholder.css("font-variant"),
777
                    weight: placeholder.css("font-weight"),
778
                    family: placeholder.css("font-family")
779
                };
780
781
            axisCount = options.xaxes.length || 1;
782
            for (i = 0; i < axisCount; ++i) {
783
784
                axisOptions = options.xaxes[i];
785
                if (axisOptions && !axisOptions.tickColor) {
786
                    axisOptions.tickColor = axisOptions.color;
787
                }
788
789
                axisOptions = $.extend(true, {}, options.xaxis, axisOptions);
790
                options.xaxes[i] = axisOptions;
791
792
                if (axisOptions.font) {
793
                    axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
794
                    if (!axisOptions.font.color) {
795
                        axisOptions.font.color = axisOptions.color;
796
                    }
797
                    if (!axisOptions.font.lineHeight) {
798
                        axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15);
799
                    }
800
                }
801
            }
802
803
            axisCount = options.yaxes.length || 1;
804
            for (i = 0; i < axisCount; ++i) {
805
806
                axisOptions = options.yaxes[i];
807
                if (axisOptions && !axisOptions.tickColor) {
808
                    axisOptions.tickColor = axisOptions.color;
809
                }
810
811
                axisOptions = $.extend(true, {}, options.yaxis, axisOptions);
812
                options.yaxes[i] = axisOptions;
813
814
                if (axisOptions.font) {
815
                    axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
816
                    if (!axisOptions.font.color) {
817
                        axisOptions.font.color = axisOptions.color;
818
                    }
819
                    if (!axisOptions.font.lineHeight) {
820
                        axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15);
821
                    }
822
                }
823
            }
824
825
            // backwards compatibility, to be removed in future
826
            if (options.xaxis.noTicks && options.xaxis.ticks == null)
827
                options.xaxis.ticks = options.xaxis.noTicks;
828
            if (options.yaxis.noTicks && options.yaxis.ticks == null)
829
                options.yaxis.ticks = options.yaxis.noTicks;
830
            if (options.x2axis) {
831
                options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis);
832
                options.xaxes[1].position = "top";
833
                // Override the inherit to allow the axis to auto-scale
834
                if (options.x2axis.min == null) {
835
                    options.xaxes[1].min = null;
836
                }
837
                if (options.x2axis.max == null) {
838
                    options.xaxes[1].max = null;
839
                }
840
            }
841
            if (options.y2axis) {
842
                options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis);
843
                options.yaxes[1].position = "right";
844
                // Override the inherit to allow the axis to auto-scale
845
                if (options.y2axis.min == null) {
846
                    options.yaxes[1].min = null;
847
                }
848
                if (options.y2axis.max == null) {
849
                    options.yaxes[1].max = null;
850
                }
851
            }
852
            if (options.grid.coloredAreas)
853
                options.grid.markings = options.grid.coloredAreas;
854
            if (options.grid.coloredAreasColor)
855
                options.grid.markingsColor = options.grid.coloredAreasColor;
856
            if (options.lines)
857
                $.extend(true, options.series.lines, options.lines);
858
            if (options.points)
859
                $.extend(true, options.series.points, options.points);
860
            if (options.bars)
861
                $.extend(true, options.series.bars, options.bars);
862
            if (options.shadowSize != null)
863
                options.series.shadowSize = options.shadowSize;
864
            if (options.highlightColor != null)
865
                options.series.highlightColor = options.highlightColor;
866
867
            // save options on axes for future reference
868
            for (i = 0; i < options.xaxes.length; ++i)
869
                getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i];
870
            for (i = 0; i < options.yaxes.length; ++i)
871
                getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i];
872
873
            // add hooks from options
874
            for (var n in hooks)
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
875
                if (options.hooks[n] && options.hooks[n].length)
876
                    hooks[n] = hooks[n].concat(options.hooks[n]);
877
878
            executeHooks(hooks.processOptions, [options]);
879
        }
880
881
        function setData(d) {
882
            series = parseData(d);
883
            fillInSeriesOptions();
884
            processData();
885
        }
886
887
        function parseData(d) {
888
            var res = [];
889
            for (var i = 0; i < d.length; ++i) {
890
                var s = $.extend(true, {}, options.series);
891
892
                if (d[i].data != null) {
893
                    s.data = d[i].data; // move the data instead of deep-copy
894
                    delete d[i].data;
895
896
                    $.extend(true, s, d[i]);
897
898
                    d[i].data = s.data;
899
                }
900
                else
901
                    s.data = d[i];
902
                res.push(s);
903
            }
904
905
            return res;
906
        }
907
908
        function axisNumber(obj, coord) {
909
            var a = obj[coord + "axis"];
910
            if (typeof a == "object") // if we got a real axis, extract number
911
                a = a.n;
912
            if (typeof a != "number")
913
                a = 1; // default to first axis
914
            return a;
915
        }
916
917
        function allAxes() {
918
            // return flat array without annoying null entries
919
            return $.grep(xaxes.concat(yaxes), function (a) { return a; });
920
        }
921
922
        function canvasToAxisCoords(pos) {
923
            // return an object with x/y corresponding to all used axes
924
            var res = {}, i, axis;
925
            for (i = 0; i < xaxes.length; ++i) {
926
                axis = xaxes[i];
927
                if (axis && axis.used)
928
                    res["x" + axis.n] = axis.c2p(pos.left);
929
            }
930
931
            for (i = 0; i < yaxes.length; ++i) {
932
                axis = yaxes[i];
933
                if (axis && axis.used)
934
                    res["y" + axis.n] = axis.c2p(pos.top);
935
            }
936
937
            if (res.x1 !== undefined)
938
                res.x = res.x1;
939
            if (res.y1 !== undefined)
940
                res.y = res.y1;
941
942
            return res;
943
        }
944
945
        function axisToCanvasCoords(pos) {
946
            // get canvas coords from the first pair of x/y found in pos
947
            var res = {}, i, axis, key;
948
949
            for (i = 0; i < xaxes.length; ++i) {
950
                axis = xaxes[i];
951
                if (axis && axis.used) {
952
                    key = "x" + axis.n;
953
                    if (pos[key] == null && axis.n == 1)
954
                        key = "x";
955
956
                    if (pos[key] != null) {
957
                        res.left = axis.p2c(pos[key]);
958
                        break;
959
                    }
960
                }
961
            }
962
963
            for (i = 0; i < yaxes.length; ++i) {
964
                axis = yaxes[i];
965
                if (axis && axis.used) {
966
                    key = "y" + axis.n;
967
                    if (pos[key] == null && axis.n == 1)
968
                        key = "y";
969
970
                    if (pos[key] != null) {
971
                        res.top = axis.p2c(pos[key]);
972
                        break;
973
                    }
974
                }
975
            }
976
977
            return res;
978
        }
979
980
        function getOrCreateAxis(axes, number) {
981
            if (!axes[number - 1])
982
                axes[number - 1] = {
983
                    n: number, // save the number for future reference
984
                    direction: axes == xaxes ? "x" : "y",
985
                    options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis)
986
                };
987
988
            return axes[number - 1];
989
        }
990
991
        function fillInSeriesOptions() {
992
993
            var neededColors = series.length, maxIndex = -1, i;
994
995
            // Subtract the number of series that already have fixed colors or
996
            // color indexes from the number that we still need to generate.
997
998
            for (i = 0; i < series.length; ++i) {
999
                var sc = series[i].color;
1000
                if (sc != null) {
1001
                    neededColors--;
1002
                    if (typeof sc == "number" && sc > maxIndex) {
1003
                        maxIndex = sc;
1004
                    }
1005
                }
1006
            }
1007
1008
            // If any of the series have fixed color indexes, then we need to
1009
            // generate at least as many colors as the highest index.
1010
1011
            if (neededColors <= maxIndex) {
1012
                neededColors = maxIndex + 1;
1013
            }
1014
1015
            // Generate all the colors, using first the option colors and then
1016
            // variations on those colors once they're exhausted.
1017
1018
            var c, colors = [], colorPool = options.colors,
1019
                colorPoolSize = colorPool.length, variation = 0;
1020
1021
            for (i = 0; i < neededColors; i++) {
1022
1023
                c = $.color.parse(colorPool[i % colorPoolSize] || "#666");
1024
1025
                // Each time we exhaust the colors in the pool we adjust
1026
                // a scaling factor used to produce more variations on
1027
                // those colors. The factor alternates negative/positive
1028
                // to produce lighter/darker colors.
1029
1030
                // Reset the variation after every few cycles, or else
1031
                // it will end up producing only white or black colors.
1032
1033
                if (i % colorPoolSize == 0 && i) {
1034
                    if (variation >= 0) {
1035
                        if (variation < 0.5) {
1036
                            variation = -variation - 0.2;
1037
                        } else variation = 0;
1038
                    } else variation = -variation;
1039
                }
1040
1041
                colors[i] = c.scale('rgb', 1 + variation);
1042
            }
1043
1044
            // Finalize the series options, filling in their colors
1045
1046
            var colori = 0, s;
1047
            for (i = 0; i < series.length; ++i) {
1048
                s = series[i];
1049
1050
                // assign colors
1051
                if (s.color == null) {
1052
                    s.color = colors[colori].toString();
1053
                    ++colori;
1054
                }
1055
                else if (typeof s.color == "number")
1056
                    s.color = colors[s.color].toString();
1057
1058
                // turn on lines automatically in case nothing is set
1059
                if (s.lines.show == null) {
1060
                    var v, show = true;
1061
                    for (v in s)
1062
                        if (s[v] && s[v].show) {
1063
                            show = false;
1064
                            break;
1065
                        }
1066
                    if (show)
1067
                        s.lines.show = true;
1068
                }
1069
1070
                // If nothing was provided for lines.zero, default it to match
1071
                // lines.fill, since areas by default should extend to zero.
1072
1073
                if (s.lines.zero == null) {
1074
                    s.lines.zero = !!s.lines.fill;
1075
                }
1076
1077
                // setup axes
1078
                s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x"));
1079
                s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y"));
1080
            }
1081
        }
1082
1083
        function processData() {
1084
            var topSentry = Number.POSITIVE_INFINITY,
1085
                bottomSentry = Number.NEGATIVE_INFINITY,
1086
                fakeInfinity = Number.MAX_VALUE,
1087
                i, j, k, m, length,
1088
                s, points, ps, x, y, axis, val, f, p,
1089
                data, format;
1090
1091
            function updateAxis(axis, min, max) {
1092
                if (min < axis.datamin && min != -fakeInfinity)
1093
                    axis.datamin = min;
1094
                if (max > axis.datamax && max != fakeInfinity)
1095
                    axis.datamax = max;
1096
            }
1097
1098
            $.each(allAxes(), function (_, axis) {
1099
                // init axis
1100
                axis.datamin = topSentry;
1101
                axis.datamax = bottomSentry;
1102
                axis.used = false;
1103
            });
1104
1105
            for (i = 0; i < series.length; ++i) {
1106
                s = series[i];
1107
                s.datapoints = { points: [] };
1108
1109
                executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]);
1110
            }
1111
1112
            // first pass: clean and copy data
1113
            for (i = 0; i < series.length; ++i) {
1114
                s = series[i];
1115
1116
                data = s.data;
1117
                format = s.datapoints.format;
1118
1119
                if (!format) {
1120
                    format = [];
1121
                    // find out how to copy
1122
                    format.push({ x: true, number: true, required: true });
1123
                    format.push({ y: true, number: true, required: true });
1124
1125
                    if (s.bars.show || (s.lines.show && s.lines.fill)) {
1126
                        var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero));
1127
                        format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale });
1128
                        if (s.bars.horizontal) {
1129
                            delete format[format.length - 1].y;
1130
                            format[format.length - 1].x = true;
1131
                        }
1132
                    }
1133
1134
                    s.datapoints.format = format;
1135
                }
1136
1137
                if (s.datapoints.pointsize != null)
1138
                    continue; // already filled in
1139
1140
                s.datapoints.pointsize = format.length;
1141
1142
                ps = s.datapoints.pointsize;
1143
                points = s.datapoints.points;
1144
1145
                var insertSteps = s.lines.show && s.lines.steps;
1146
                s.xaxis.used = s.yaxis.used = true;
1147
1148
                for (j = k = 0; j < data.length; ++j, k += ps) {
1149
                    p = data[j];
1150
1151
                    var nullify = p == null;
1152
                    if (!nullify) {
1153
                        for (m = 0; m < ps; ++m) {
1154
                            val = p[m];
1155
                            f = format[m];
1156
1157
                            if (f) {
1158
                                if (f.number && val != null) {
1159
                                    val = +val; // convert to number
1160
                                    if (isNaN(val))
1161
                                        val = null;
1162
                                    else if (val == Infinity)
1163
                                        val = fakeInfinity;
1164
                                    else if (val == -Infinity)
1165
                                        val = -fakeInfinity;
1166
                                }
1167
1168
                                if (val == null) {
1169
                                    if (f.required)
1170
                                        nullify = true;
1171
1172
                                    if (f.defaultValue != null)
1173
                                        val = f.defaultValue;
1174
                                }
1175
                            }
1176
1177
                            points[k + m] = val;
1178
                        }
1179
                    }
1180
1181
                    if (nullify) {
1182
                        for (m = 0; m < ps; ++m) {
1183
                            val = points[k + m];
1184
                            if (val != null) {
1185
                                f = format[m];
1186
                                // extract min/max info
1187
                                if (f.autoscale !== false) {
1188
                                    if (f.x) {
1189
                                        updateAxis(s.xaxis, val, val);
1190
                                    }
1191
                                    if (f.y) {
1192
                                        updateAxis(s.yaxis, val, val);
1193
                                    }
1194
                                }
1195
                            }
1196
                            points[k + m] = null;
1197
                        }
1198
                    }
1199
                    else {
1200
                        // a little bit of line specific stuff that
1201
                        // perhaps shouldn't be here, but lacking
1202
                        // better means...
1203
                        if (insertSteps && k > 0
1204
                            && points[k - ps] != null
1205
                            && points[k - ps] != points[k]
1206
                            && points[k - ps + 1] != points[k + 1]) {
1207
                            // copy the point to make room for a middle point
1208
                            for (m = 0; m < ps; ++m)
1209
                                points[k + ps + m] = points[k + m];
1210
1211
                            // middle point has same y
1212
                            points[k + 1] = points[k - ps + 1];
1213
1214
                            // we've added a point, better reflect that
1215
                            k += ps;
1216
                        }
1217
                    }
1218
                }
1219
            }
1220
1221
            // give the hooks a chance to run
1222
            for (i = 0; i < series.length; ++i) {
1223
                s = series[i];
1224
1225
                executeHooks(hooks.processDatapoints, [ s, s.datapoints]);
1226
            }
1227
1228
            // second pass: find datamax/datamin for auto-scaling
1229
            for (i = 0; i < series.length; ++i) {
1230
                s = series[i];
1231
                points = s.datapoints.points;
1232
                ps = s.datapoints.pointsize;
1233
                format = s.datapoints.format;
1234
1235
                var xmin = topSentry, ymin = topSentry,
1236
                    xmax = bottomSentry, ymax = bottomSentry;
1237
1238
                for (j = 0; j < points.length; j += ps) {
1239
                    if (points[j] == null)
1240
                        continue;
1241
1242
                    for (m = 0; m < ps; ++m) {
1243
                        val = points[j + m];
1244
                        f = format[m];
1245
                        if (!f || f.autoscale === false || val == fakeInfinity || val == -fakeInfinity)
1246
                            continue;
1247
1248
                        if (f.x) {
1249
                            if (val < xmin)
1250
                                xmin = val;
1251
                            if (val > xmax)
1252
                                xmax = val;
1253
                        }
1254
                        if (f.y) {
1255
                            if (val < ymin)
1256
                                ymin = val;
1257
                            if (val > ymax)
1258
                                ymax = val;
1259
                        }
1260
                    }
1261
                }
1262
1263
                if (s.bars.show) {
1264
                    // make sure we got room for the bar on the dancing floor
1265
                    var delta;
1266
1267
                    switch (s.bars.align) {
1268
                        case "left":
1269
                            delta = 0;
1270
                            break;
1271
                        case "right":
1272
                            delta = -s.bars.barWidth;
1273
                            break;
1274
                        default:
1275
                            delta = -s.bars.barWidth / 2;
1276
                    }
1277
1278
                    if (s.bars.horizontal) {
1279
                        ymin += delta;
1280
                        ymax += delta + s.bars.barWidth;
1281
                    }
1282
                    else {
1283
                        xmin += delta;
1284
                        xmax += delta + s.bars.barWidth;
1285
                    }
1286
                }
1287
1288
                updateAxis(s.xaxis, xmin, xmax);
1289
                updateAxis(s.yaxis, ymin, ymax);
1290
            }
1291
1292
            $.each(allAxes(), function (_, axis) {
1293
                if (axis.datamin == topSentry)
1294
                    axis.datamin = null;
1295
                if (axis.datamax == bottomSentry)
1296
                    axis.datamax = null;
1297
            });
1298
        }
1299
1300
        function setupCanvases() {
1301
1302
            // Make sure the placeholder is clear of everything except canvases
1303
            // from a previous plot in this container that we'll try to re-use.
1304
1305
            placeholder.css("padding", 0) // padding messes up the positioning
1306
                .children().filter(function(){
1307
                    return !$(this).hasClass("flot-overlay") && !$(this).hasClass('flot-base');
1308
                }).remove();
1309
1310
            if (placeholder.css("position") == 'static')
1311
                placeholder.css("position", "relative"); // for positioning labels and overlay
1312
1313
            surface = new Canvas("flot-base", placeholder);
1314
            overlay = new Canvas("flot-overlay", placeholder); // overlay canvas for interactive features
1315
1316
            ctx = surface.context;
1317
            octx = overlay.context;
1318
1319
            // define which element we're listening for events on
1320
            eventHolder = $(overlay.element).unbind();
1321
1322
            // If we're re-using a plot object, shut down the old one
1323
1324
            var existing = placeholder.data("plot");
1325
1326
            if (existing) {
1327
                existing.shutdown();
1328
                overlay.clear();
1329
            }
1330
1331
            // save in case we get replotted
1332
            placeholder.data("plot", plot);
1333
        }
1334
1335
        function bindEvents() {
1336
            // bind events
1337
            if (options.grid.hoverable) {
1338
                eventHolder.mousemove(onMouseMove);
1339
1340
                // Use bind, rather than .mouseleave, because we officially
1341
                // still support jQuery 1.2.6, which doesn't define a shortcut
1342
                // for mouseenter or mouseleave.  This was a bug/oversight that
1343
                // was fixed somewhere around 1.3.x.  We can return to using
1344
                // .mouseleave when we drop support for 1.2.6.
1345
1346
                eventHolder.bind("mouseleave", onMouseLeave);
1347
            }
1348
1349
            if (options.grid.clickable)
1350
                eventHolder.click(onClick);
1351
1352
            executeHooks(hooks.bindEvents, [eventHolder]);
1353
        }
1354
1355
        function shutdown() {
1356
            if (redrawTimeout)
1357
                clearTimeout(redrawTimeout);
1358
1359
            eventHolder.unbind("mousemove", onMouseMove);
1360
            eventHolder.unbind("mouseleave", onMouseLeave);
1361
            eventHolder.unbind("click", onClick);
1362
1363
            executeHooks(hooks.shutdown, [eventHolder]);
1364
        }
1365
1366
        function setTransformationHelpers(axis) {
1367
            // set helper functions on the axis, assumes plot area
1368
            // has been computed already
1369
1370
            function identity(x) { return x; }
1371
1372
            var s, m, t = axis.options.transform || identity,
1373
                it = axis.options.inverseTransform;
1374
1375
            // precompute how much the axis is scaling a point
1376
            // in canvas space
1377
            if (axis.direction == "x") {
1378
                s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min));
1379
                m = Math.min(t(axis.max), t(axis.min));
1380
            }
1381
            else {
1382
                s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min));
1383
                s = -s;
1384
                m = Math.max(t(axis.max), t(axis.min));
1385
            }
1386
1387
            // data point to canvas coordinate
1388
            if (t == identity) // slight optimization
1389
                axis.p2c = function (p) { return (p - m) * s; };
1390
            else
1391
                axis.p2c = function (p) { return (t(p) - m) * s; };
1392
            // canvas coordinate to data point
1393
            if (!it)
1394
                axis.c2p = function (c) { return m + c / s; };
1395
            else
1396
                axis.c2p = function (c) { return it(m + c / s); };
1397
        }
1398
1399
        function measureTickLabels(axis) {
1400
1401
            var opts = axis.options,
1402
                ticks = axis.ticks || [],
1403
                labelWidth = opts.labelWidth || 0,
1404
                labelHeight = opts.labelHeight || 0,
1405
                maxWidth = labelWidth || (axis.direction == "x" ? Math.floor(surface.width / (ticks.length || 1)) : null),
1406
                legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis",
1407
                layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles,
1408
                font = opts.font || "flot-tick-label tickLabel";
1409
1410
            for (var i = 0; i < ticks.length; ++i) {
1411
1412
                var t = ticks[i];
1413
1414
                if (!t.label)
1415
                    continue;
1416
1417
                var info = surface.getTextInfo(layer, t.label, font, null, maxWidth);
1418
1419
                labelWidth = Math.max(labelWidth, info.width);
1420
                labelHeight = Math.max(labelHeight, info.height);
1421
            }
1422
1423
            axis.labelWidth = opts.labelWidth || labelWidth;
1424
            axis.labelHeight = opts.labelHeight || labelHeight;
1425
        }
1426
1427
        function allocateAxisBoxFirstPhase(axis) {
1428
            // find the bounding box of the axis by looking at label
1429
            // widths/heights and ticks, make room by diminishing the
1430
            // plotOffset; this first phase only looks at one
1431
            // dimension per axis, the other dimension depends on the
1432
            // other axes so will have to wait
1433
1434
            var lw = axis.labelWidth,
1435
                lh = axis.labelHeight,
1436
                pos = axis.options.position,
1437
                isXAxis = axis.direction === "x",
1438
                tickLength = axis.options.tickLength,
1439
                axisMargin = options.grid.axisMargin,
1440
                padding = options.grid.labelMargin,
1441
                innermost = true,
1442
                outermost = true,
1443
                first = true,
1444
                found = false;
1445
1446
            // Determine the axis's position in its direction and on its side
1447
1448
            $.each(isXAxis ? xaxes : yaxes, function(i, a) {
1449
                if (a && (a.show || a.reserveSpace)) {
1450
                    if (a === axis) {
1451
                        found = true;
1452
                    } else if (a.options.position === pos) {
1453
                        if (found) {
1454
                            outermost = false;
1455
                        } else {
1456
                            innermost = false;
1457
                        }
1458
                    }
1459
                    if (!found) {
1460
                        first = false;
1461
                    }
1462
                }
1463
            });
1464
1465
            // The outermost axis on each side has no margin
1466
1467
            if (outermost) {
1468
                axisMargin = 0;
1469
            }
1470
1471
            // The ticks for the first axis in each direction stretch across
1472
1473
            if (tickLength == null) {
1474
                tickLength = first ? "full" : 5;
1475
            }
1476
1477
            if (!isNaN(+tickLength))
1478
                padding += +tickLength;
1479
1480
            if (isXAxis) {
1481
                lh += padding;
1482
1483
                if (pos == "bottom") {
1484
                    plotOffset.bottom += lh + axisMargin;
1485
                    axis.box = { top: surface.height - plotOffset.bottom, height: lh };
1486
                }
1487
                else {
1488
                    axis.box = { top: plotOffset.top + axisMargin, height: lh };
1489
                    plotOffset.top += lh + axisMargin;
1490
                }
1491
            }
1492
            else {
1493
                lw += padding;
1494
1495
                if (pos == "left") {
1496
                    axis.box = { left: plotOffset.left + axisMargin, width: lw };
1497
                    plotOffset.left += lw + axisMargin;
1498
                }
1499
                else {
1500
                    plotOffset.right += lw + axisMargin;
1501
                    axis.box = { left: surface.width - plotOffset.right, width: lw };
1502
                }
1503
            }
1504
1505
             // save for future reference
1506
            axis.position = pos;
1507
            axis.tickLength = tickLength;
1508
            axis.box.padding = padding;
1509
            axis.innermost = innermost;
1510
        }
1511
1512
        function allocateAxisBoxSecondPhase(axis) {
1513
            // now that all axis boxes have been placed in one
1514
            // dimension, we can set the remaining dimension coordinates
1515
            if (axis.direction == "x") {
1516
                axis.box.left = plotOffset.left - axis.labelWidth / 2;
1517
                axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth;
1518
            }
1519
            else {
1520
                axis.box.top = plotOffset.top - axis.labelHeight / 2;
1521
                axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight;
1522
            }
1523
        }
1524
1525
        function adjustLayoutForThingsStickingOut() {
1526
            // possibly adjust plot offset to ensure everything stays
1527
            // inside the canvas and isn't clipped off
1528
1529
            var minMargin = options.grid.minBorderMargin,
1530
                axis, i;
1531
1532
            // check stuff from the plot (FIXME: this should just read
1533
            // a value from the series, otherwise it's impossible to
1534
            // customize)
1535
            if (minMargin == null) {
1536
                minMargin = 0;
1537
                for (i = 0; i < series.length; ++i)
1538
                    minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth/2));
1539
            }
1540
1541
            var margins = {
1542
                left: minMargin,
1543
                right: minMargin,
1544
                top: minMargin,
1545
                bottom: minMargin
1546
            };
1547
1548
            // check axis labels, note we don't check the actual
1549
            // labels but instead use the overall width/height to not
1550
            // jump as much around with replots
1551
            $.each(allAxes(), function (_, axis) {
1552
                if (axis.reserveSpace && axis.ticks && axis.ticks.length) {
1553
                    if (axis.direction === "x") {
1554
                        margins.left = Math.max(margins.left, axis.labelWidth / 2);
1555
                        margins.right = Math.max(margins.right, axis.labelWidth / 2);
1556
                    } else {
1557
                        margins.bottom = Math.max(margins.bottom, axis.labelHeight / 2);
1558
                        margins.top = Math.max(margins.top, axis.labelHeight / 2);
1559
                    }
1560
                }
1561
            });
1562
1563
            plotOffset.left = Math.ceil(Math.max(margins.left, plotOffset.left));
1564
            plotOffset.right = Math.ceil(Math.max(margins.right, plotOffset.right));
1565
            plotOffset.top = Math.ceil(Math.max(margins.top, plotOffset.top));
1566
            plotOffset.bottom = Math.ceil(Math.max(margins.bottom, plotOffset.bottom));
1567
        }
1568
1569
        function setupGrid() {
1570
            var i, axes = allAxes(), showGrid = options.grid.show;
1571
1572
            // Initialize the plot's offset from the edge of the canvas
1573
1574
            for (var a in plotOffset) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
1575
                var margin = options.grid.margin || 0;
1576
                plotOffset[a] = typeof margin == "number" ? margin : margin[a] || 0;
1577
            }
1578
1579
            executeHooks(hooks.processOffset, [plotOffset]);
1580
1581
            // If the grid is visible, add its border width to the offset
1582
1583
            for (var a in plotOffset) {
1584
                if(typeof(options.grid.borderWidth) == "object") {
1585
                    plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0;
1586
                }
1587
                else {
1588
                    plotOffset[a] += showGrid ? options.grid.borderWidth : 0;
1589
                }
1590
            }
1591
1592
            $.each(axes, function (_, axis) {
1593
                var axisOpts = axis.options;
1594
                axis.show = axisOpts.show == null ? axis.used : axisOpts.show;
1595
                axis.reserveSpace = axisOpts.reserveSpace == null ? axis.show : axisOpts.reserveSpace;
1596
                setRange(axis);
1597
            });
1598
1599
            if (showGrid) {
1600
1601
                var allocatedAxes = $.grep(axes, function (axis) {
1602
                    return axis.show || axis.reserveSpace;
1603
                });
1604
1605
                $.each(allocatedAxes, function (_, axis) {
1606
                    // make the ticks
1607
                    setupTickGeneration(axis);
1608
                    setTicks(axis);
1609
                    snapRangeToTicks(axis, axis.ticks);
1610
                    // find labelWidth/Height for axis
1611
                    measureTickLabels(axis);
1612
                });
1613
1614
                // with all dimensions calculated, we can compute the
1615
                // axis bounding boxes, start from the outside
1616
                // (reverse order)
1617
                for (i = allocatedAxes.length - 1; i >= 0; --i)
1618
                    allocateAxisBoxFirstPhase(allocatedAxes[i]);
1619
1620
                // make sure we've got enough space for things that
1621
                // might stick out
1622
                adjustLayoutForThingsStickingOut();
1623
1624
                $.each(allocatedAxes, function (_, axis) {
1625
                    allocateAxisBoxSecondPhase(axis);
1626
                });
1627
            }
1628
1629
            plotWidth = surface.width - plotOffset.left - plotOffset.right;
1630
            plotHeight = surface.height - plotOffset.bottom - plotOffset.top;
1631
1632
            // now we got the proper plot dimensions, we can compute the scaling
1633
            $.each(axes, function (_, axis) {
1634
                setTransformationHelpers(axis);
1635
            });
1636
1637
            if (showGrid) {
1638
                drawAxisLabels();
1639
            }
1640
1641
            insertLegend();
1642
        }
1643
1644
        function setRange(axis) {
1645
            var opts = axis.options,
1646
                min = +(opts.min != null ? opts.min : axis.datamin),
1647
                max = +(opts.max != null ? opts.max : axis.datamax),
1648
                delta = max - min;
1649
1650
            if (delta == 0.0) {
1651
                // degenerate case
1652
                var widen = max == 0 ? 1 : 0.01;
1653
1654
                if (opts.min == null)
1655
                    min -= widen;
1656
                // always widen max if we couldn't widen min to ensure we
1657
                // don't fall into min == max which doesn't work
1658
                if (opts.max == null || opts.min != null)
1659
                    max += widen;
1660
            }
1661
            else {
1662
                // consider autoscaling
1663
                var margin = opts.autoscaleMargin;
1664
                if (margin != null) {
1665
                    if (opts.min == null) {
1666
                        min -= delta * margin;
1667
                        // make sure we don't go below zero if all values
1668
                        // are positive
1669
                        if (min < 0 && axis.datamin != null && axis.datamin >= 0)
1670
                            min = 0;
1671
                    }
1672
                    if (opts.max == null) {
1673
                        max += delta * margin;
1674
                        if (max > 0 && axis.datamax != null && axis.datamax <= 0)
1675
                            max = 0;
1676
                    }
1677
                }
1678
            }
1679
            axis.min = min;
1680
            axis.max = max;
1681
        }
1682
1683
        function setupTickGeneration(axis) {
1684
            var opts = axis.options;
1685
1686
            // estimate number of ticks
1687
            var noTicks;
1688
            if (typeof opts.ticks == "number" && opts.ticks > 0)
1689
                noTicks = opts.ticks;
1690
            else
1691
                // heuristic based on the model a*sqrt(x) fitted to
1692
                // some data points that seemed reasonable
1693
                noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? surface.width : surface.height);
1694
1695
            var delta = (axis.max - axis.min) / noTicks,
1696
                dec = -Math.floor(Math.log(delta) / Math.LN10),
1697
                maxDec = opts.tickDecimals;
1698
1699
            if (maxDec != null && dec > maxDec) {
1700
                dec = maxDec;
1701
            }
1702
1703
            var magn = Math.pow(10, -dec),
1704
                norm = delta / magn, // norm is between 1.0 and 10.0
1705
                size;
1706
1707
            if (norm < 1.5) {
1708
                size = 1;
1709
            } else if (norm < 3) {
1710
                size = 2;
1711
                // special case for 2.5, requires an extra decimal
1712
                if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
1713
                    size = 2.5;
1714
                    ++dec;
1715
                }
1716
            } else if (norm < 7.5) {
1717
                size = 5;
1718
            } else {
1719
                size = 10;
1720
            }
1721
1722
            size *= magn;
1723
1724
            if (opts.minTickSize != null && size < opts.minTickSize) {
1725
                size = opts.minTickSize;
1726
            }
1727
1728
            axis.delta = delta;
1729
            axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
1730
            axis.tickSize = opts.tickSize || size;
1731
1732
            // Time mode was moved to a plug-in in 0.8, and since so many people use it
1733
            // we'll add an especially friendly reminder to make sure they included it.
1734
1735
            if (opts.mode == "time" && !axis.tickGenerator) {
1736
                throw new Error("Time mode requires the flot.time plugin.");
1737
            }
1738
1739
            // Flot supports base-10 axes; any other mode else is handled by a plug-in,
1740
            // like flot.time.js.
1741
1742
            if (!axis.tickGenerator) {
1743
1744
                axis.tickGenerator = function (axis) {
1745
1746
                    var ticks = [],
1747
                        start = floorInBase(axis.min, axis.tickSize),
1748
                        i = 0,
1749
                        v = Number.NaN,
1750
                        prev;
1751
1752
                    do {
1753
                        prev = v;
1754
                        v = start + i * axis.tickSize;
1755
                        ticks.push(v);
1756
                        ++i;
1757
                    } while (v < axis.max && v != prev);
1758
                    return ticks;
1759
                };
1760
1761
				axis.tickFormatter = function (value, axis) {
1762
1763
					var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1;
1764
					var formatted = "" + Math.round(value * factor) / factor;
1765
1766
					// If tickDecimals was specified, ensure that we have exactly that
1767
					// much precision; otherwise default to the value's own precision.
1768
1769
					if (axis.tickDecimals != null) {
1770
						var decimal = formatted.indexOf(".");
1771
						var precision = decimal == -1 ? 0 : formatted.length - decimal - 1;
1772
						if (precision < axis.tickDecimals) {
1773
							return (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision);
1774
						}
1775
					}
1776
1777
                    return formatted;
1778
                };
1779
            }
1780
1781
            if ($.isFunction(opts.tickFormatter))
1782
                axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); };
1783
1784
            if (opts.alignTicksWithAxis != null) {
1785
                var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1];
1786
                if (otherAxis && otherAxis.used && otherAxis != axis) {
1787
                    // consider snapping min/max to outermost nice ticks
1788
                    var niceTicks = axis.tickGenerator(axis);
1789
                    if (niceTicks.length > 0) {
1790
                        if (opts.min == null)
1791
                            axis.min = Math.min(axis.min, niceTicks[0]);
1792
                        if (opts.max == null && niceTicks.length > 1)
1793
                            axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]);
1794
                    }
1795
1796
                    axis.tickGenerator = function (axis) {
1797
                        // copy ticks, scaled to this axis
1798
                        var ticks = [], v, i;
1799
                        for (i = 0; i < otherAxis.ticks.length; ++i) {
1800
                            v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min);
1801
                            v = axis.min + v * (axis.max - axis.min);
1802
                            ticks.push(v);
1803
                        }
1804
                        return ticks;
1805
                    };
1806
1807
                    // we might need an extra decimal since forced
1808
                    // ticks don't necessarily fit naturally
1809
                    if (!axis.mode && opts.tickDecimals == null) {
1810
                        var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1),
1811
                            ts = axis.tickGenerator(axis);
1812
1813
                        // only proceed if the tick interval rounded
1814
                        // with an extra decimal doesn't give us a
1815
                        // zero at end
1816
                        if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec))))
1817
                            axis.tickDecimals = extraDec;
1818
                    }
1819
                }
1820
            }
1821
        }
1822
1823
        function setTicks(axis) {
1824
            var oticks = axis.options.ticks, ticks = [];
1825
            if (oticks == null || (typeof oticks == "number" && oticks > 0))
1826
                ticks = axis.tickGenerator(axis);
1827
            else if (oticks) {
1828
                if ($.isFunction(oticks))
1829
                    // generate the ticks
1830
                    ticks = oticks(axis);
1831
                else
1832
                    ticks = oticks;
1833
            }
1834
1835
            // clean up/labelify the supplied ticks, copy them over
1836
            var i, v;
1837
            axis.ticks = [];
1838
            for (i = 0; i < ticks.length; ++i) {
1839
                var label = null;
1840
                var t = ticks[i];
1841
                if (typeof t == "object") {
1842
                    v = +t[0];
1843
                    if (t.length > 1)
1844
                        label = t[1];
1845
                }
1846
                else
1847
                    v = +t;
1848
                if (label == null)
1849
                    label = axis.tickFormatter(v, axis);
1850
                if (!isNaN(v))
1851
                    axis.ticks.push({ v: v, label: label });
1852
            }
1853
        }
1854
1855
        function snapRangeToTicks(axis, ticks) {
1856
            if (axis.options.autoscaleMargin && ticks.length > 0) {
1857
                // snap to ticks
1858
                if (axis.options.min == null)
1859
                    axis.min = Math.min(axis.min, ticks[0].v);
1860
                if (axis.options.max == null && ticks.length > 1)
1861
                    axis.max = Math.max(axis.max, ticks[ticks.length - 1].v);
1862
            }
1863
        }
1864
1865
        function draw() {
1866
1867
            surface.clear();
1868
1869
            executeHooks(hooks.drawBackground, [ctx]);
1870
1871
            var grid = options.grid;
1872
1873
            // draw background, if any
1874
            if (grid.show && grid.backgroundColor)
1875
                drawBackground();
1876
1877
            if (grid.show && !grid.aboveData) {
1878
                drawGrid();
1879
            }
1880
1881
            for (var i = 0; i < series.length; ++i) {
1882
                executeHooks(hooks.drawSeries, [ctx, series[i]]);
1883
                drawSeries(series[i]);
1884
            }
1885
1886
            executeHooks(hooks.draw, [ctx]);
1887
1888
            if (grid.show && grid.aboveData) {
1889
                drawGrid();
1890
            }
1891
1892
            surface.render();
1893
1894
            // A draw implies that either the axes or data have changed, so we
1895
            // should probably update the overlay highlights as well.
1896
1897
            triggerRedrawOverlay();
1898
        }
1899
1900
        function extractRange(ranges, coord) {
1901
            var axis, from, to, key, axes = allAxes();
1902
1903
            for (var i = 0; i < axes.length; ++i) {
1904
                axis = axes[i];
1905
                if (axis.direction == coord) {
1906
                    key = coord + axis.n + "axis";
1907
                    if (!ranges[key] && axis.n == 1)
1908
                        key = coord + "axis"; // support x1axis as xaxis
1909
                    if (ranges[key]) {
1910
                        from = ranges[key].from;
1911
                        to = ranges[key].to;
1912
                        break;
1913
                    }
1914
                }
1915
            }
1916
1917
            // backwards-compat stuff - to be removed in future
1918
            if (!ranges[key]) {
0 ignored issues
show
Bug introduced by
The variable key seems to not be initialized for all possible execution paths.
Loading history...
1919
                axis = coord == "x" ? xaxes[0] : yaxes[0];
1920
                from = ranges[coord + "1"];
1921
                to = ranges[coord + "2"];
1922
            }
1923
1924
            // auto-reverse as an added bonus
1925
            if (from != null && to != null && from > to) {
0 ignored issues
show
Bug introduced by
The variable to seems to not be initialized for all possible execution paths.
Loading history...
Bug introduced by
The variable from seems to not be initialized for all possible execution paths.
Loading history...
1926
                var tmp = from;
1927
                from = to;
1928
                to = tmp;
1929
            }
1930
1931
            return { from: from, to: to, axis: axis };
0 ignored issues
show
Bug introduced by
The variable axis seems to not be initialized for all possible execution paths.
Loading history...
1932
        }
1933
1934
        function drawBackground() {
1935
            ctx.save();
1936
            ctx.translate(plotOffset.left, plotOffset.top);
1937
1938
            ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)");
1939
            ctx.fillRect(0, 0, plotWidth, plotHeight);
1940
            ctx.restore();
1941
        }
1942
1943
        function drawGrid() {
1944
            var i, axes, bw, bc;
1945
1946
            ctx.save();
1947
            ctx.translate(plotOffset.left, plotOffset.top);
1948
1949
            // draw markings
1950
            var markings = options.grid.markings;
1951
            if (markings) {
1952
                if ($.isFunction(markings)) {
1953
                    axes = plot.getAxes();
1954
                    // xmin etc. is backwards compatibility, to be
1955
                    // removed in the future
1956
                    axes.xmin = axes.xaxis.min;
1957
                    axes.xmax = axes.xaxis.max;
1958
                    axes.ymin = axes.yaxis.min;
1959
                    axes.ymax = axes.yaxis.max;
1960
1961
                    markings = markings(axes);
1962
                }
1963
1964
                for (i = 0; i < markings.length; ++i) {
1965
                    var m = markings[i],
1966
                        xrange = extractRange(m, "x"),
1967
                        yrange = extractRange(m, "y");
1968
1969
                    // fill in missing
1970
                    if (xrange.from == null)
1971
                        xrange.from = xrange.axis.min;
1972
                    if (xrange.to == null)
1973
                        xrange.to = xrange.axis.max;
1974
                    if (yrange.from == null)
1975
                        yrange.from = yrange.axis.min;
1976
                    if (yrange.to == null)
1977
                        yrange.to = yrange.axis.max;
1978
1979
                    // clip
1980
                    if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max ||
1981
                        yrange.to < yrange.axis.min || yrange.from > yrange.axis.max)
1982
                        continue;
1983
1984
                    xrange.from = Math.max(xrange.from, xrange.axis.min);
1985
                    xrange.to = Math.min(xrange.to, xrange.axis.max);
1986
                    yrange.from = Math.max(yrange.from, yrange.axis.min);
1987
                    yrange.to = Math.min(yrange.to, yrange.axis.max);
1988
1989
                    var xequal = xrange.from === xrange.to,
1990
                        yequal = yrange.from === yrange.to;
1991
1992
                    if (xequal && yequal) {
1993
                        continue;
1994
                    }
1995
1996
                    // then draw
1997
                    xrange.from = Math.floor(xrange.axis.p2c(xrange.from));
1998
                    xrange.to = Math.floor(xrange.axis.p2c(xrange.to));
1999
                    yrange.from = Math.floor(yrange.axis.p2c(yrange.from));
2000
                    yrange.to = Math.floor(yrange.axis.p2c(yrange.to));
2001
2002
                    if (xequal || yequal) {
2003
                        var lineWidth = m.lineWidth || options.grid.markingsLineWidth,
2004
                            subPixel = lineWidth % 2 ? 0.5 : 0;
2005
                        ctx.beginPath();
2006
                        ctx.strokeStyle = m.color || options.grid.markingsColor;
2007
                        ctx.lineWidth = lineWidth;
2008
                        if (xequal) {
2009
                            ctx.moveTo(xrange.to + subPixel, yrange.from);
2010
                            ctx.lineTo(xrange.to + subPixel, yrange.to);
2011
                        } else {
2012
                            ctx.moveTo(xrange.from, yrange.to + subPixel);
2013
                            ctx.lineTo(xrange.to, yrange.to + subPixel);                            
2014
                        }
2015
                        ctx.stroke();
2016
                    } else {
2017
                        ctx.fillStyle = m.color || options.grid.markingsColor;
2018
                        ctx.fillRect(xrange.from, yrange.to,
2019
                                     xrange.to - xrange.from,
2020
                                     yrange.from - yrange.to);
2021
                    }
2022
                }
2023
            }
2024
2025
            // draw the ticks
2026
            axes = allAxes();
2027
            bw = options.grid.borderWidth;
2028
2029
            for (var j = 0; j < axes.length; ++j) {
2030
                var axis = axes[j], box = axis.box,
2031
                    t = axis.tickLength, x, y, xoff, yoff;
2032
                if (!axis.show || axis.ticks.length == 0)
2033
                    continue;
2034
2035
                ctx.lineWidth = 1;
2036
2037
                // find the edges
2038
                if (axis.direction == "x") {
2039
                    x = 0;
2040
                    if (t == "full")
2041
                        y = (axis.position == "top" ? 0 : plotHeight);
2042
                    else
2043
                        y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0);
2044
                }
2045
                else {
2046
                    y = 0;
2047
                    if (t == "full")
2048
                        x = (axis.position == "left" ? 0 : plotWidth);
2049
                    else
2050
                        x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0);
2051
                }
2052
2053
                // draw tick bar
2054
                if (!axis.innermost) {
2055
                    ctx.strokeStyle = axis.options.color;
2056
                    ctx.beginPath();
2057
                    xoff = yoff = 0;
2058
                    if (axis.direction == "x")
2059
                        xoff = plotWidth + 1;
2060
                    else
2061
                        yoff = plotHeight + 1;
2062
2063
                    if (ctx.lineWidth == 1) {
2064
                        if (axis.direction == "x") {
2065
                            y = Math.floor(y) + 0.5;
2066
                        } else {
2067
                            x = Math.floor(x) + 0.5;
2068
                        }
2069
                    }
2070
2071
                    ctx.moveTo(x, y);
2072
                    ctx.lineTo(x + xoff, y + yoff);
2073
                    ctx.stroke();
2074
                }
2075
2076
                // draw ticks
2077
2078
                ctx.strokeStyle = axis.options.tickColor;
2079
2080
                ctx.beginPath();
2081
                for (i = 0; i < axis.ticks.length; ++i) {
2082
                    var v = axis.ticks[i].v;
2083
2084
                    xoff = yoff = 0;
2085
2086
                    if (isNaN(v) || v < axis.min || v > axis.max
2087
                        // skip those lying on the axes if we got a border
2088
                        || (t == "full"
2089
                            && ((typeof bw == "object" && bw[axis.position] > 0) || bw > 0)
2090
                            && (v == axis.min || v == axis.max)))
2091
                        continue;
2092
2093
                    if (axis.direction == "x") {
2094
                        x = axis.p2c(v);
2095
                        yoff = t == "full" ? -plotHeight : t;
2096
2097
                        if (axis.position == "top")
2098
                            yoff = -yoff;
2099
                    }
2100
                    else {
2101
                        y = axis.p2c(v);
2102
                        xoff = t == "full" ? -plotWidth : t;
2103
2104
                        if (axis.position == "left")
2105
                            xoff = -xoff;
2106
                    }
2107
2108
                    if (ctx.lineWidth == 1) {
2109
                        if (axis.direction == "x")
2110
                            x = Math.floor(x) + 0.5;
2111
                        else
2112
                            y = Math.floor(y) + 0.5;
2113
                    }
2114
2115
                    ctx.moveTo(x, y);
2116
                    ctx.lineTo(x + xoff, y + yoff);
2117
                }
2118
2119
                ctx.stroke();
2120
            }
2121
2122
2123
            // draw border
2124
            if (bw) {
2125
                // If either borderWidth or borderColor is an object, then draw the border
2126
                // line by line instead of as one rectangle
2127
                bc = options.grid.borderColor;
2128
                if(typeof bw == "object" || typeof bc == "object") {
2129
                    if (typeof bw !== "object") {
2130
                        bw = {top: bw, right: bw, bottom: bw, left: bw};
2131
                    }
2132
                    if (typeof bc !== "object") {
2133
                        bc = {top: bc, right: bc, bottom: bc, left: bc};
2134
                    }
2135
2136
                    if (bw.top > 0) {
2137
                        ctx.strokeStyle = bc.top;
2138
                        ctx.lineWidth = bw.top;
2139
                        ctx.beginPath();
2140
                        ctx.moveTo(0 - bw.left, 0 - bw.top/2);
2141
                        ctx.lineTo(plotWidth, 0 - bw.top/2);
2142
                        ctx.stroke();
2143
                    }
2144
2145
                    if (bw.right > 0) {
2146
                        ctx.strokeStyle = bc.right;
2147
                        ctx.lineWidth = bw.right;
2148
                        ctx.beginPath();
2149
                        ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top);
2150
                        ctx.lineTo(plotWidth + bw.right / 2, plotHeight);
2151
                        ctx.stroke();
2152
                    }
2153
2154
                    if (bw.bottom > 0) {
2155
                        ctx.strokeStyle = bc.bottom;
2156
                        ctx.lineWidth = bw.bottom;
2157
                        ctx.beginPath();
2158
                        ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2);
2159
                        ctx.lineTo(0, plotHeight + bw.bottom / 2);
2160
                        ctx.stroke();
2161
                    }
2162
2163
                    if (bw.left > 0) {
2164
                        ctx.strokeStyle = bc.left;
2165
                        ctx.lineWidth = bw.left;
2166
                        ctx.beginPath();
2167
                        ctx.moveTo(0 - bw.left/2, plotHeight + bw.bottom);
2168
                        ctx.lineTo(0- bw.left/2, 0);
2169
                        ctx.stroke();
2170
                    }
2171
                }
2172
                else {
2173
                    ctx.lineWidth = bw;
2174
                    ctx.strokeStyle = options.grid.borderColor;
2175
                    ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw);
2176
                }
2177
            }
2178
2179
            ctx.restore();
2180
        }
2181
2182
        function drawAxisLabels() {
2183
2184
            $.each(allAxes(), function (_, axis) {
2185
                var box = axis.box,
2186
                    legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis",
2187
                    layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles,
2188
                    font = axis.options.font || "flot-tick-label tickLabel",
2189
                    tick, x, y, halign, valign;
2190
2191
                // Remove text before checking for axis.show and ticks.length;
2192
                // otherwise plugins, like flot-tickrotor, that draw their own
2193
                // tick labels will end up with both theirs and the defaults.
2194
2195
                surface.removeText(layer);
2196
2197
                if (!axis.show || axis.ticks.length == 0)
2198
                    return;
2199
2200
                for (var i = 0; i < axis.ticks.length; ++i) {
2201
2202
                    tick = axis.ticks[i];
2203
                    if (!tick.label || tick.v < axis.min || tick.v > axis.max)
2204
                        continue;
2205
2206
                    if (axis.direction == "x") {
2207
                        halign = "center";
2208
                        x = plotOffset.left + axis.p2c(tick.v);
2209
                        if (axis.position == "bottom") {
2210
                            y = box.top + box.padding;
2211
                        } else {
2212
                            y = box.top + box.height - box.padding;
2213
                            valign = "bottom";
2214
                        }
2215
                    } else {
2216
                        valign = "middle";
2217
                        y = plotOffset.top + axis.p2c(tick.v);
2218
                        if (axis.position == "left") {
2219
                            x = box.left + box.width - box.padding;
2220
                            halign = "right";
2221
                        } else {
2222
                            x = box.left + box.padding;
2223
                        }
2224
                    }
2225
2226
                    surface.addText(layer, x, y, tick.label, font, null, null, halign, valign);
0 ignored issues
show
Bug introduced by
The variable halign seems to not be initialized for all possible execution paths. Are you sure addText handles undefined variables?
Loading history...
Bug introduced by
The variable valign seems to not be initialized for all possible execution paths. Are you sure addText handles undefined variables?
Loading history...
2227
                }
2228
            });
2229
        }
2230
2231
        function drawSeries(series) {
2232
            if (series.lines.show)
2233
                drawSeriesLines(series);
2234
            if (series.bars.show)
2235
                drawSeriesBars(series);
2236
            if (series.points.show)
2237
                drawSeriesPoints(series);
2238
        }
2239
2240
        function drawSeriesLines(series) {
2241
            function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
2242
                var points = datapoints.points,
2243
                    ps = datapoints.pointsize,
2244
                    prevx = null, prevy = null;
2245
2246
                ctx.beginPath();
2247
                for (var i = ps; i < points.length; i += ps) {
2248
                    var x1 = points[i - ps], y1 = points[i - ps + 1],
2249
                        x2 = points[i], y2 = points[i + 1];
2250
2251
                    if (x1 == null || x2 == null)
2252
                        continue;
2253
2254
                    // clip with ymin
2255
                    if (y1 <= y2 && y1 < axisy.min) {
2256
                        if (y2 < axisy.min)
2257
                            continue;   // line segment is outside
2258
                        // compute new intersection point
2259
                        x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
2260
                        y1 = axisy.min;
2261
                    }
2262
                    else if (y2 <= y1 && y2 < axisy.min) {
2263
                        if (y1 < axisy.min)
2264
                            continue;
2265
                        x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
2266
                        y2 = axisy.min;
2267
                    }
2268
2269
                    // clip with ymax
2270
                    if (y1 >= y2 && y1 > axisy.max) {
2271
                        if (y2 > axisy.max)
2272
                            continue;
2273
                        x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
2274
                        y1 = axisy.max;
2275
                    }
2276
                    else if (y2 >= y1 && y2 > axisy.max) {
2277
                        if (y1 > axisy.max)
2278
                            continue;
2279
                        x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
2280
                        y2 = axisy.max;
2281
                    }
2282
2283
                    // clip with xmin
2284
                    if (x1 <= x2 && x1 < axisx.min) {
2285
                        if (x2 < axisx.min)
2286
                            continue;
2287
                        y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
2288
                        x1 = axisx.min;
2289
                    }
2290
                    else if (x2 <= x1 && x2 < axisx.min) {
2291
                        if (x1 < axisx.min)
2292
                            continue;
2293
                        y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
2294
                        x2 = axisx.min;
2295
                    }
2296
2297
                    // clip with xmax
2298
                    if (x1 >= x2 && x1 > axisx.max) {
2299
                        if (x2 > axisx.max)
2300
                            continue;
2301
                        y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
2302
                        x1 = axisx.max;
2303
                    }
2304
                    else if (x2 >= x1 && x2 > axisx.max) {
2305
                        if (x1 > axisx.max)
2306
                            continue;
2307
                        y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
2308
                        x2 = axisx.max;
2309
                    }
2310
2311
                    if (x1 != prevx || y1 != prevy)
2312
                        ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
2313
2314
                    prevx = x2;
2315
                    prevy = y2;
2316
                    ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
2317
                }
2318
                ctx.stroke();
2319
            }
2320
2321
            function plotLineArea(datapoints, axisx, axisy) {
2322
                var points = datapoints.points,
2323
                    ps = datapoints.pointsize,
2324
                    bottom = Math.min(Math.max(0, axisy.min), axisy.max),
2325
                    i = 0, top, areaOpen = false,
0 ignored issues
show
Unused Code introduced by
The variable top seems to be never used. Consider removing it.
Loading history...
2326
                    ypos = 1, segmentStart = 0, segmentEnd = 0;
2327
2328
                // we process each segment in two turns, first forward
2329
                // direction to sketch out top, then once we hit the
2330
                // end we go backwards to sketch the bottom
2331
                while (true) {
2332
                    if (ps > 0 && i > points.length + ps)
2333
                        break;
2334
2335
                    i += ps; // ps is negative if going backwards
2336
2337
                    var x1 = points[i - ps],
2338
                        y1 = points[i - ps + ypos],
2339
                        x2 = points[i], y2 = points[i + ypos];
2340
2341
                    if (areaOpen) {
2342
                        if (ps > 0 && x1 != null && x2 == null) {
2343
                            // at turning point
2344
                            segmentEnd = i;
2345
                            ps = -ps;
2346
                            ypos = 2;
2347
                            continue;
2348
                        }
2349
2350
                        if (ps < 0 && i == segmentStart + ps) {
2351
                            // done with the reverse sweep
2352
                            ctx.fill();
2353
                            areaOpen = false;
2354
                            ps = -ps;
2355
                            ypos = 1;
2356
                            i = segmentStart = segmentEnd + ps;
2357
                            continue;
2358
                        }
2359
                    }
2360
2361
                    if (x1 == null || x2 == null)
2362
                        continue;
2363
2364
                    // clip x values
2365
2366
                    // clip with xmin
2367
                    if (x1 <= x2 && x1 < axisx.min) {
2368
                        if (x2 < axisx.min)
2369
                            continue;
2370
                        y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
2371
                        x1 = axisx.min;
2372
                    }
2373
                    else if (x2 <= x1 && x2 < axisx.min) {
2374
                        if (x1 < axisx.min)
2375
                            continue;
2376
                        y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
2377
                        x2 = axisx.min;
2378
                    }
2379
2380
                    // clip with xmax
2381
                    if (x1 >= x2 && x1 > axisx.max) {
2382
                        if (x2 > axisx.max)
2383
                            continue;
2384
                        y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
2385
                        x1 = axisx.max;
2386
                    }
2387
                    else if (x2 >= x1 && x2 > axisx.max) {
2388
                        if (x1 > axisx.max)
2389
                            continue;
2390
                        y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
2391
                        x2 = axisx.max;
2392
                    }
2393
2394
                    if (!areaOpen) {
2395
                        // open area
2396
                        ctx.beginPath();
2397
                        ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
2398
                        areaOpen = true;
2399
                    }
2400
2401
                    // now first check the case where both is outside
2402
                    if (y1 >= axisy.max && y2 >= axisy.max) {
2403
                        ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
2404
                        ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
2405
                        continue;
2406
                    }
2407
                    else if (y1 <= axisy.min && y2 <= axisy.min) {
2408
                        ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
2409
                        ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
2410
                        continue;
2411
                    }
2412
2413
                    // else it's a bit more complicated, there might
2414
                    // be a flat maxed out rectangle first, then a
2415
                    // triangular cutout or reverse; to find these
2416
                    // keep track of the current x values
2417
                    var x1old = x1, x2old = x2;
2418
2419
                    // clip the y values, without shortcutting, we
2420
                    // go through all cases in turn
2421
2422
                    // clip with ymin
2423
                    if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
2424
                        x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
2425
                        y1 = axisy.min;
2426
                    }
2427
                    else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
2428
                        x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
2429
                        y2 = axisy.min;
2430
                    }
2431
2432
                    // clip with ymax
2433
                    if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
2434
                        x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
2435
                        y1 = axisy.max;
2436
                    }
2437
                    else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
2438
                        x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
2439
                        y2 = axisy.max;
2440
                    }
2441
2442
                    // if the x value was changed we got a rectangle
2443
                    // to fill
2444
                    if (x1 != x1old) {
2445
                        ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1));
2446
                        // it goes to (x1, y1), but we fill that below
2447
                    }
2448
2449
                    // fill triangular section, this sometimes result
2450
                    // in redundant points if (x1, y1) hasn't changed
2451
                    // from previous line to, but we just ignore that
2452
                    ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
2453
                    ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
2454
2455
                    // fill the other rectangle if it's there
2456
                    if (x2 != x2old) {
2457
                        ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
2458
                        ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2));
2459
                    }
2460
                }
2461
            }
2462
2463
            ctx.save();
2464
            ctx.translate(plotOffset.left, plotOffset.top);
2465
            ctx.lineJoin = "round";
2466
2467
            var lw = series.lines.lineWidth,
2468
                sw = series.shadowSize;
2469
            // FIXME: consider another form of shadow when filling is turned on
2470
            if (lw > 0 && sw > 0) {
2471
                // draw shadow as a thick and thin line with transparency
2472
                ctx.lineWidth = sw;
2473
                ctx.strokeStyle = "rgba(0,0,0,0.1)";
2474
                // position shadow at angle from the mid of line
2475
                var angle = Math.PI/18;
2476
                plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis);
2477
                ctx.lineWidth = sw/2;
2478
                plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis);
2479
            }
2480
2481
            ctx.lineWidth = lw;
2482
            ctx.strokeStyle = series.color;
2483
            var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
2484
            if (fillStyle) {
2485
                ctx.fillStyle = fillStyle;
2486
                plotLineArea(series.datapoints, series.xaxis, series.yaxis);
2487
            }
2488
2489
            if (lw > 0)
2490
                plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis);
2491
            ctx.restore();
2492
        }
2493
2494
        function drawSeriesPoints(series) {
2495
            function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) {
2496
                var points = datapoints.points, ps = datapoints.pointsize;
2497
2498
                for (var i = 0; i < points.length; i += ps) {
2499
                    var x = points[i], y = points[i + 1];
2500
                    if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
2501
                        continue;
2502
2503
                    ctx.beginPath();
2504
                    x = axisx.p2c(x);
2505
                    y = axisy.p2c(y) + offset;
2506
                    if (symbol == "circle")
2507
                        ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false);
2508
                    else
2509
                        symbol(ctx, x, y, radius, shadow);
2510
                    ctx.closePath();
2511
2512
                    if (fillStyle) {
2513
                        ctx.fillStyle = fillStyle;
2514
                        ctx.fill();
2515
                    }
2516
                    ctx.stroke();
2517
                }
2518
            }
2519
2520
            ctx.save();
2521
            ctx.translate(plotOffset.left, plotOffset.top);
2522
2523
            var lw = series.points.lineWidth,
2524
                sw = series.shadowSize,
2525
                radius = series.points.radius,
2526
                symbol = series.points.symbol;
2527
2528
            // If the user sets the line width to 0, we change it to a very 
2529
            // small value. A line width of 0 seems to force the default of 1.
2530
            // Doing the conditional here allows the shadow setting to still be 
2531
            // optional even with a lineWidth of 0.
2532
2533
            if( lw == 0 )
2534
                lw = 0.0001;
2535
2536
            if (lw > 0 && sw > 0) {
2537
                // draw shadow in two steps
2538
                var w = sw / 2;
2539
                ctx.lineWidth = w;
2540
                ctx.strokeStyle = "rgba(0,0,0,0.1)";
2541
                plotPoints(series.datapoints, radius, null, w + w/2, true,
2542
                           series.xaxis, series.yaxis, symbol);
2543
2544
                ctx.strokeStyle = "rgba(0,0,0,0.2)";
2545
                plotPoints(series.datapoints, radius, null, w/2, true,
2546
                           series.xaxis, series.yaxis, symbol);
2547
            }
2548
2549
            ctx.lineWidth = lw;
2550
            ctx.strokeStyle = series.color;
2551
            plotPoints(series.datapoints, radius,
2552
                       getFillStyle(series.points, series.color), 0, false,
2553
                       series.xaxis, series.yaxis, symbol);
2554
            ctx.restore();
2555
        }
2556
2557
        function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) {
2558
            var left, right, bottom, top,
2559
                drawLeft, drawRight, drawTop, drawBottom,
2560
                tmp;
2561
2562
            // in horizontal mode, we start the bar from the left
2563
            // instead of from the bottom so it appears to be
2564
            // horizontal rather than vertical
2565
            if (horizontal) {
2566
                drawBottom = drawRight = drawTop = true;
2567
                drawLeft = false;
2568
                left = b;
2569
                right = x;
2570
                top = y + barLeft;
2571
                bottom = y + barRight;
2572
2573
                // account for negative bars
2574
                if (right < left) {
2575
                    tmp = right;
2576
                    right = left;
2577
                    left = tmp;
2578
                    drawLeft = true;
2579
                    drawRight = false;
2580
                }
2581
            }
2582
            else {
2583
                drawLeft = drawRight = drawTop = true;
2584
                drawBottom = false;
2585
                left = x + barLeft;
2586
                right = x + barRight;
2587
                bottom = b;
2588
                top = y;
2589
2590
                // account for negative bars
2591
                if (top < bottom) {
2592
                    tmp = top;
2593
                    top = bottom;
2594
                    bottom = tmp;
2595
                    drawBottom = true;
2596
                    drawTop = false;
2597
                }
2598
            }
2599
2600
            // clip
2601
            if (right < axisx.min || left > axisx.max ||
2602
                top < axisy.min || bottom > axisy.max)
2603
                return;
2604
2605
            if (left < axisx.min) {
2606
                left = axisx.min;
2607
                drawLeft = false;
2608
            }
2609
2610
            if (right > axisx.max) {
2611
                right = axisx.max;
2612
                drawRight = false;
2613
            }
2614
2615
            if (bottom < axisy.min) {
2616
                bottom = axisy.min;
2617
                drawBottom = false;
2618
            }
2619
2620
            if (top > axisy.max) {
2621
                top = axisy.max;
2622
                drawTop = false;
2623
            }
2624
2625
            left = axisx.p2c(left);
2626
            bottom = axisy.p2c(bottom);
2627
            right = axisx.p2c(right);
2628
            top = axisy.p2c(top);
2629
2630
            // fill the bar
2631
            if (fillStyleCallback) {
2632
                c.fillStyle = fillStyleCallback(bottom, top);
2633
                c.fillRect(left, top, right - left, bottom - top)
2634
            }
2635
2636
            // draw outline
2637
            if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) {
2638
                c.beginPath();
2639
2640
                // FIXME: inline moveTo is buggy with excanvas
2641
                c.moveTo(left, bottom);
2642
                if (drawLeft)
2643
                    c.lineTo(left, top);
2644
                else
2645
                    c.moveTo(left, top);
2646
                if (drawTop)
2647
                    c.lineTo(right, top);
2648
                else
2649
                    c.moveTo(right, top);
2650
                if (drawRight)
2651
                    c.lineTo(right, bottom);
2652
                else
2653
                    c.moveTo(right, bottom);
2654
                if (drawBottom)
2655
                    c.lineTo(left, bottom);
2656
                else
2657
                    c.moveTo(left, bottom);
2658
                c.stroke();
2659
            }
2660
        }
2661
2662
        function drawSeriesBars(series) {
2663
            function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) {
2664
                var points = datapoints.points, ps = datapoints.pointsize;
2665
2666
                for (var i = 0; i < points.length; i += ps) {
2667
                    if (points[i] == null)
2668
                        continue;
2669
                    drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth);
2670
                }
2671
            }
2672
2673
            ctx.save();
2674
            ctx.translate(plotOffset.left, plotOffset.top);
2675
2676
            // FIXME: figure out a way to add shadows (for instance along the right edge)
2677
            ctx.lineWidth = series.bars.lineWidth;
2678
            ctx.strokeStyle = series.color;
2679
2680
            var barLeft;
2681
2682
            switch (series.bars.align) {
2683
                case "left":
2684
                    barLeft = 0;
2685
                    break;
2686
                case "right":
2687
                    barLeft = -series.bars.barWidth;
2688
                    break;
2689
                default:
2690
                    barLeft = -series.bars.barWidth / 2;
2691
            }
2692
2693
            var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null;
2694
            plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, fillStyleCallback, series.xaxis, series.yaxis);
2695
            ctx.restore();
2696
        }
2697
2698
        function getFillStyle(filloptions, seriesColor, bottom, top) {
2699
            var fill = filloptions.fill;
2700
            if (!fill)
2701
                return null;
2702
2703
            if (filloptions.fillColor)
2704
                return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
2705
2706
            var c = $.color.parse(seriesColor);
2707
            c.a = typeof fill == "number" ? fill : 0.4;
2708
            c.normalize();
2709
            return c.toString();
2710
        }
2711
2712
        function insertLegend() {
2713
2714
            if (options.legend.container != null) {
2715
                $(options.legend.container).html("");
2716
            } else {
2717
                placeholder.find(".legend").remove();
2718
            }
2719
2720
            if (!options.legend.show) {
2721
                return;
2722
            }
2723
2724
            var fragments = [], entries = [], rowStarted = false,
2725
                lf = options.legend.labelFormatter, s, label;
2726
2727
            // Build a list of legend entries, with each having a label and a color
2728
2729
            for (var i = 0; i < series.length; ++i) {
2730
                s = series[i];
2731
                if (s.label) {
2732
                    label = lf ? lf(s.label, s) : s.label;
2733
                    if (label) {
2734
                        entries.push({
2735
                            label: label,
2736
                            color: s.color
2737
                        });
2738
                    }
2739
                }
2740
            }
2741
2742
            // Sort the legend using either the default or a custom comparator
2743
2744
            if (options.legend.sorted) {
2745
                if ($.isFunction(options.legend.sorted)) {
2746
                    entries.sort(options.legend.sorted);
2747
                } else if (options.legend.sorted == "reverse") {
2748
                	entries.reverse();
2749
                } else {
2750
                    var ascending = options.legend.sorted != "descending";
2751
                    entries.sort(function(a, b) {
2752
                        return a.label == b.label ? 0 : (
2753
                            (a.label < b.label) != ascending ? 1 : -1   // Logical XOR
2754
                        );
2755
                    });
2756
                }
2757
            }
2758
2759
            // Generate markup for the list of entries, in their final order
2760
2761
            for (var i = 0; i < entries.length; ++i) {
2762
2763
                var entry = entries[i];
2764
2765
                if (i % options.legend.noColumns == 0) {
2766
                    if (rowStarted)
2767
                        fragments.push('</tr>');
2768
                    fragments.push('<tr>');
2769
                    rowStarted = true;
2770
                }
2771
2772
                fragments.push(
2773
                    '<td class="legendColorBox"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:4px;height:0;border:5px solid ' + entry.color + ';overflow:hidden"></div></div></td>' +
2774
                    '<td class="legendLabel">' + entry.label + '</td>'
2775
                );
2776
            }
2777
2778
            if (rowStarted)
2779
                fragments.push('</tr>');
2780
2781
            if (fragments.length == 0)
2782
                return;
2783
2784
            var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>';
2785
            if (options.legend.container != null)
2786
                $(options.legend.container).html(table);
2787
            else {
2788
                var pos = "",
2789
                    p = options.legend.position,
2790
                    m = options.legend.margin;
2791
                if (m[0] == null)
2792
                    m = [m, m];
2793
                if (p.charAt(0) == "n")
2794
                    pos += 'top:' + (m[1] + plotOffset.top) + 'px;';
2795
                else if (p.charAt(0) == "s")
2796
                    pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;';
2797
                if (p.charAt(1) == "e")
2798
                    pos += 'right:' + (m[0] + plotOffset.right) + 'px;';
2799
                else if (p.charAt(1) == "w")
2800
                    pos += 'left:' + (m[0] + plotOffset.left) + 'px;';
2801
                var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos +';') + '</div>').appendTo(placeholder);
2802
                if (options.legend.backgroundOpacity != 0.0) {
2803
                    // put in the transparent background
2804
                    // separately to avoid blended labels and
2805
                    // label boxes
2806
                    var c = options.legend.backgroundColor;
2807
                    if (c == null) {
2808
                        c = options.grid.backgroundColor;
2809
                        if (c && typeof c == "string")
2810
                            c = $.color.parse(c);
2811
                        else
2812
                            c = $.color.extract(legend, 'background-color');
2813
                        c.a = 1;
2814
                        c = c.toString();
2815
                    }
2816
                    var div = legend.children();
2817
                    $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity);
2818
                }
2819
            }
2820
        }
2821
2822
2823
        // interactive features
2824
2825
        var highlights = [],
2826
            redrawTimeout = null;
2827
2828
        // returns the data item the mouse is over, or null if none is found
2829
        function findNearbyItem(mouseX, mouseY, seriesFilter) {
2830
            var maxDistance = options.grid.mouseActiveRadius,
2831
                smallestDistance = maxDistance * maxDistance + 1,
2832
                item = null, foundPoint = false, i, j, ps;
0 ignored issues
show
Unused Code introduced by
The variable foundPoint seems to be never used. Consider removing it.
Loading history...
2833
2834
            for (i = series.length - 1; i >= 0; --i) {
2835
                if (!seriesFilter(series[i]))
2836
                    continue;
2837
2838
                var s = series[i],
2839
                    axisx = s.xaxis,
2840
                    axisy = s.yaxis,
2841
                    points = s.datapoints.points,
2842
                    mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster
2843
                    my = axisy.c2p(mouseY),
2844
                    maxx = maxDistance / axisx.scale,
2845
                    maxy = maxDistance / axisy.scale;
2846
2847
                ps = s.datapoints.pointsize;
2848
                // with inverse transforms, we can't use the maxx/maxy
2849
                // optimization, sadly
2850
                if (axisx.options.inverseTransform)
2851
                    maxx = Number.MAX_VALUE;
2852
                if (axisy.options.inverseTransform)
2853
                    maxy = Number.MAX_VALUE;
2854
2855
                if (s.lines.show || s.points.show) {
2856
                    for (j = 0; j < points.length; j += ps) {
2857
                        var x = points[j], y = points[j + 1];
2858
                        if (x == null)
2859
                            continue;
2860
2861
                        // For points and lines, the cursor must be within a
2862
                        // certain distance to the data point
2863
                        if (x - mx > maxx || x - mx < -maxx ||
2864
                            y - my > maxy || y - my < -maxy)
2865
                            continue;
2866
2867
                        // We have to calculate distances in pixels, not in
2868
                        // data units, because the scales of the axes may be different
2869
                        var dx = Math.abs(axisx.p2c(x) - mouseX),
2870
                            dy = Math.abs(axisy.p2c(y) - mouseY),
2871
                            dist = dx * dx + dy * dy; // we save the sqrt
2872
2873
                        // use <= to ensure last point takes precedence
2874
                        // (last generally means on top of)
2875
                        if (dist < smallestDistance) {
2876
                            smallestDistance = dist;
2877
                            item = [i, j / ps];
2878
                        }
2879
                    }
2880
                }
2881
2882
                if (s.bars.show && !item) { // no other point can be nearby
2883
2884
                    var barLeft, barRight;
2885
2886
                    switch (s.bars.align) {
2887
                        case "left":
2888
                            barLeft = 0;
2889
                            break;
2890
                        case "right":
2891
                            barLeft = -s.bars.barWidth;
2892
                            break;
2893
                        default:
2894
                            barLeft = -s.bars.barWidth / 2;
2895
                    }
2896
2897
                    barRight = barLeft + s.bars.barWidth;
2898
2899
                    for (j = 0; j < points.length; j += ps) {
2900
                        var x = points[j], y = points[j + 1], b = points[j + 2];
2901
                        if (x == null)
2902
                            continue;
2903
2904
                        // for a bar graph, the cursor must be inside the bar
2905
                        if (series[i].bars.horizontal ?
2906
                            (mx <= Math.max(b, x) && mx >= Math.min(b, x) &&
2907
                             my >= y + barLeft && my <= y + barRight) :
2908
                            (mx >= x + barLeft && mx <= x + barRight &&
2909
                             my >= Math.min(b, y) && my <= Math.max(b, y)))
2910
                                item = [i, j / ps];
2911
                    }
2912
                }
2913
            }
2914
2915
            if (item) {
2916
                i = item[0];
0 ignored issues
show
Complexity Coding Style introduced by
You seem to be assigning a new value to the loop variable i here. Please check if this was indeed your intention. Even if it was, consider using another kind of loop instead.
Loading history...
2917
                j = item[1];
2918
                ps = series[i].datapoints.pointsize;
2919
2920
                return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps),
2921
                         dataIndex: j,
2922
                         series: series[i],
2923
                         seriesIndex: i };
2924
            }
2925
2926
            return null;
2927
        }
2928
2929
        function onMouseMove(e) {
2930
            if (options.grid.hoverable)
2931
                triggerClickHoverEvent("plothover", e,
2932
                                       function (s) { return s["hoverable"] != false; });
2933
        }
2934
2935
        function onMouseLeave(e) {
2936
            if (options.grid.hoverable)
2937
                triggerClickHoverEvent("plothover", e,
2938
                                       function (s) { return false; });
2939
        }
2940
2941
        function onClick(e) {
2942
            triggerClickHoverEvent("plotclick", e,
2943
                                   function (s) { return s["clickable"] != false; });
2944
        }
2945
2946
        // trigger click or hover event (they send the same parameters
2947
        // so we share their code)
2948
        function triggerClickHoverEvent(eventname, event, seriesFilter) {
2949
            var offset = eventHolder.offset(),
2950
                canvasX = event.pageX - offset.left - plotOffset.left,
2951
                canvasY = event.pageY - offset.top - plotOffset.top,
2952
            pos = canvasToAxisCoords({ left: canvasX, top: canvasY });
2953
2954
            pos.pageX = event.pageX;
2955
            pos.pageY = event.pageY;
2956
2957
            var item = findNearbyItem(canvasX, canvasY, seriesFilter);
2958
2959
            if (item) {
2960
                // fill in mouse pos for any listeners out there
2961
                item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left, 10);
2962
                item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top, 10);
2963
            }
2964
2965
            if (options.grid.autoHighlight) {
2966
                // clear auto-highlights
2967
                for (var i = 0; i < highlights.length; ++i) {
2968
                    var h = highlights[i];
2969
                    if (h.auto == eventname &&
2970
                        !(item && h.series == item.series &&
2971
                          h.point[0] == item.datapoint[0] &&
2972
                          h.point[1] == item.datapoint[1]))
2973
                        unhighlight(h.series, h.point);
2974
                }
2975
2976
                if (item)
2977
                    highlight(item.series, item.datapoint, eventname);
2978
            }
2979
2980
            placeholder.trigger(eventname, [ pos, item ]);
2981
        }
2982
2983
        function triggerRedrawOverlay() {
2984
            var t = options.interaction.redrawOverlayInterval;
2985
            if (t == -1) {      // skip event queue
2986
                drawOverlay();
2987
                return;
2988
            }
2989
2990
            if (!redrawTimeout)
2991
                redrawTimeout = setTimeout(drawOverlay, t);
2992
        }
2993
2994
        function drawOverlay() {
2995
            redrawTimeout = null;
2996
2997
            // draw highlights
2998
            octx.save();
2999
            overlay.clear();
3000
            octx.translate(plotOffset.left, plotOffset.top);
3001
3002
            var i, hi;
3003
            for (i = 0; i < highlights.length; ++i) {
3004
                hi = highlights[i];
3005
3006
                if (hi.series.bars.show)
3007
                    drawBarHighlight(hi.series, hi.point);
3008
                else
3009
                    drawPointHighlight(hi.series, hi.point);
3010
            }
3011
            octx.restore();
3012
3013
            executeHooks(hooks.drawOverlay, [octx]);
3014
        }
3015
3016
        function highlight(s, point, auto) {
3017
            if (typeof s == "number")
3018
                s = series[s];
3019
3020
            if (typeof point == "number") {
3021
                var ps = s.datapoints.pointsize;
3022
                point = s.datapoints.points.slice(ps * point, ps * (point + 1));
3023
            }
3024
3025
            var i = indexOfHighlight(s, point);
3026
            if (i == -1) {
3027
                highlights.push({ series: s, point: point, auto: auto });
3028
3029
                triggerRedrawOverlay();
3030
            }
3031
            else if (!auto)
3032
                highlights[i].auto = false;
3033
        }
3034
3035
        function unhighlight(s, point) {
3036
            if (s == null && point == null) {
3037
                highlights = [];
3038
                triggerRedrawOverlay();
3039
                return;
3040
            }
3041
3042
            if (typeof s == "number")
3043
                s = series[s];
3044
3045
            if (typeof point == "number") {
3046
                var ps = s.datapoints.pointsize;
3047
                point = s.datapoints.points.slice(ps * point, ps * (point + 1));
3048
            }
3049
3050
            var i = indexOfHighlight(s, point);
3051
            if (i != -1) {
3052
                highlights.splice(i, 1);
3053
3054
                triggerRedrawOverlay();
3055
            }
3056
        }
3057
3058
        function indexOfHighlight(s, p) {
3059
            for (var i = 0; i < highlights.length; ++i) {
3060
                var h = highlights[i];
3061
                if (h.series == s && h.point[0] == p[0]
3062
                    && h.point[1] == p[1])
3063
                    return i;
3064
            }
3065
            return -1;
3066
        }
3067
3068
        function drawPointHighlight(series, point) {
3069
            var x = point[0], y = point[1],
3070
                axisx = series.xaxis, axisy = series.yaxis,
3071
                highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString();
3072
3073
            if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
3074
                return;
3075
3076
            var pointRadius = series.points.radius + series.points.lineWidth / 2;
3077
            octx.lineWidth = pointRadius;
3078
            octx.strokeStyle = highlightColor;
3079
            var radius = 1.5 * pointRadius;
3080
            x = axisx.p2c(x);
3081
            y = axisy.p2c(y);
3082
3083
            octx.beginPath();
3084
            if (series.points.symbol == "circle")
3085
                octx.arc(x, y, radius, 0, 2 * Math.PI, false);
3086
            else
3087
                series.points.symbol(octx, x, y, radius, false);
3088
            octx.closePath();
3089
            octx.stroke();
3090
        }
3091
3092
        function drawBarHighlight(series, point) {
3093
            var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(),
3094
                fillStyle = highlightColor,
3095
                barLeft;
3096
3097
            switch (series.bars.align) {
3098
                case "left":
3099
                    barLeft = 0;
3100
                    break;
3101
                case "right":
3102
                    barLeft = -series.bars.barWidth;
3103
                    break;
3104
                default:
3105
                    barLeft = -series.bars.barWidth / 2;
3106
            }
3107
3108
            octx.lineWidth = series.bars.lineWidth;
3109
            octx.strokeStyle = highlightColor;
3110
3111
            drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth,
3112
                    function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth);
3113
        }
3114
3115
        function getColorOrGradient(spec, bottom, top, defaultColor) {
3116
            if (typeof spec == "string")
3117
                return spec;
3118
            else {
3119
                // assume this is a gradient spec; IE currently only
3120
                // supports a simple vertical gradient properly, so that's
3121
                // what we support too
3122
                var gradient = ctx.createLinearGradient(0, top, 0, bottom);
3123
3124
                for (var i = 0, l = spec.colors.length; i < l; ++i) {
3125
                    var c = spec.colors[i];
3126
                    if (typeof c != "string") {
3127
                        var co = $.color.parse(defaultColor);
3128
                        if (c.brightness != null)
3129
                            co = co.scale('rgb', c.brightness);
3130
                        if (c.opacity != null)
3131
                            co.a *= c.opacity;
3132
                        c = co.toString();
3133
                    }
3134
                    gradient.addColorStop(i / (l - 1), c);
3135
                }
3136
3137
                return gradient;
3138
            }
3139
        }
3140
    }
3141
3142
    // Add the plot function to the top level of the jQuery object
3143
3144
    $.plot = function(placeholder, data, options) {
3145
        //var t0 = new Date();
3146
        var plot = new Plot($(placeholder), data, options, $.plot.plugins);
3147
        //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime()));
3148
        return plot;
3149
    };
3150
3151
    $.plot.version = "0.8.3";
3152
3153
    $.plot.plugins = [];
3154
3155
    // Also add the plot function as a chainable property
3156
3157
    $.fn.plot = function(data, options) {
3158
        return this.each(function() {
3159
            $.plot(this, data, options);
3160
        });
3161
    };
3162
3163
    // round to nearby lower multiple of base
3164
    function floorInBase(n, base) {
3165
        return base * Math.floor(n / base);
3166
    }
3167
3168
})(jQuery);
3169